Claude Code 플러그인, 통째로 깔지 않고 알맹이만 빼왔다

지난 편 → 6편 — 챗GPT랑 클로드, 왜 서로 대화는 못 시킬까 Claude Code로 한동안 일하다 보면, 남이 만들어 둔 도구를 가져다 쓰고 싶어지는 순간이 옵니다. 마켓플레이스에 올라온 플러그인 중 하나가 눈에 들어왔습니다. 코드를 필요 이상으로 부풀리지 않게, "게으른 시니어 개발자"처럼 최소로만 짜도록 붙잡아 주는 도구였습니다. 마음에 들었습니다. 그런데 막상 설치하려니 손이 멈췄습니다. 제가 원한 건 그 안에 담긴 원칙 하나 였는데, 플러그인은 그것 말고도 이런저런 것들을 통째로 데려옵니다. 안 쓸 기능까지 제 환경에 얹히는 게 영 내키지 않았습니다. 그래서 깔지 않고, 열어서 필요한 알맹이만 빼내기로 했습니다. 그러려면 먼저 "플러그인"과 "스킬"이 뭐가 다른지부터 알아야 했습니다. 스킬과 플러그인은 뭐가 다른가 한 줄로 하면 이렇습니다. 스킬은 부품 하나, 플러그인은 부품을 담은 상자. 스킬(Skill) 은 최소 단위입니다. SKILL.md 라는 마크다운 파일 하나가 전부고, 필요하면 옆에 보조 파일(스크립트·데이터)을 둘 수 있습니다. 두는 곳도 단순합니다. 개인용: ~/.claude/skills/<이름>/SKILL.md 프로젝트용: .claude/skills/<이름>/SKILL.md 파일 맨 위 frontmatter에 "이 스킬을 언제 써야 하는지"를 적어두면, Claude가 상황을 보고 알아서 불러 씁니다. 아니면 /스킬이름 으로 직접 부를 수도 있습니다. 말하자면 "이 일은 이렇게 처리해라"라고 적어둔 지침 한 장 입니다. 플러그인(Plugin) 은 그 부품들을 담아 배포하는 상자입니다. .claude-plugin/plugin.json 이라는 매니페스트를 중심으로, 여러 종류의 구성요소를 한 번에 묶습니다. skills/ — 스킬들 agents/ — 커스텀 에이전트(서브에이전트...

공고 알림봇 만들기 (4편) — API마다 응답 구조가 달랐습니다

3편에서 주소를 겨우 찾았다고 했죠. 그래서 다섯 곳에 다 요청을 보냈어요. 그랬더니 돌아온 것들이 서로 하나도 안 닮았습니다.

지난 편 → 3편 — 목록에 없는 API를 직접 찾아냈습니다

다섯 곳이 다섯 가지 모양이었어요

같은 "공고 목록"을 달라고 했는데 이렇게 왔어요.

소스형식목록이 어디 있나
K-StartupXMLitem 태그들
기업마당JSONjsonArray
LHJSON배열의 두 번째 칸
마이홈JSONresponse → body → item
든든전세JSON그냥 최상위 배열

XML 하나에 JSON 넷인데, 그 JSON 넷도 목록 위치가 전부 달라요. 표준이 있는 줄 알았는데 아무도 같은 걸 안 쓰고 있었어요.

XML인데 필드명이 태그가 아니었어요

제일 당황스러웠던 건 K-Startup이었어요. XML이면 보통 이런 모양을 기대하잖아요.

<item>
  <공고명>청년창업 지원사업</공고명>
</item>

그런데 실제로는 이랬어요.

<item>
  <col name="biz_pbanc_nm">청년창업 지원사업</col>
</item>

전부 col이라는 똑같은 태그예요. 필드 이름은 태그가 아니라 속성 안에 들어 있고요.

이러면 "공고명 태그를 찾아줘"가 안 통해요. col을 전부 훑으면서 name 속성을 읽어 이름표를 붙여줘야 합니다. 한 줄이면 되는 일이 두 단계가 된 거죠.

dsList라는 복병

LH는 더 예상 밖이었어요. JSON을 열었더니 최상위가 객체가 아니라 배열이었고, 그 안에 두 덩어리가 들어 있었어요.

[ { "dsSch": [ ... ] },
  { "dsList": [ ... ] } ]

dsSch는 제가 보낸 검색 조건이 되돌아온 거고, 실제 공고 목록은 dsList 쪽에 있었어요. 요청과 응답이 한 봉투에 같이 담겨 온 셈이죠.

ds가 뭘 줄인 건지는 어디에도 안 적혀 있어요. 내부 시스템에서 쓰던 이름이 그대로 밖으로 나온 것처럼 보이는데, 확인할 방법은 없었습니다.

그래서 "배열의 두 번째 걸 꺼내라"고 짜지 않고, 배열을 돌면서 dsList 키를 가진 덩어리를 찾도록 했어요. 순서가 언제 바뀔지 모르니까요.

한 건일 때만 모양이 바뀌어요

마이홈에서는 다른 종류의 함정을 만났어요. 평소엔 목록이 배열로 오는데, 결과가 딱 한 건일 때는 배열이 아니라 그냥 객체 하나로 옵니다.

  • 여러 건: [ {...}, {...} ]
  • 한 건: {...}

이게 고약한 이유는 평소엔 멀쩡하다가 어느 날 갑자기 터지기 때문이에요. 공고가 여러 개일 땐 잘 돌다가, 하필 한 건만 남은 날 목록으로 처리하려다 깨집니다. 그것도 새벽에요.

그래서 받은 게 객체면 원소 하나짜리 배열로 감싸고 넘어가게 했어요. 몇 줄 안 되는데, 이런 게 없으면 어느 날 조용히 멈춰 있습니다.

같은 걸 부르는 이름이 네 가지였어요

구조를 통과해도 필드 이름이 또 제각각이었어요. 똑같이 "공고 제목"과 "상세 링크"인데 이렇습니다.

소스공고명상세 링크
K-Startupbiz_pbanc_nmdetl_pg_url
기업마당pblancNmpblancUrl
LHPAN_NMDTL_URL
마이홈pblancNmpcUrl
든든전세없음없음

소문자에 밑줄, 낙타등, 전부 대문자가 다 섞여 있어요. 기업마당과 마이홈만 pblancNm으로 우연히 겹치고, 그마저 링크 이름은 다릅니다.

든든전세는 아예 제목 필드가 없어서 지역·유형·면적을 조합해 제목을 만들어 붙였어요. 3편에서 상세 링크가 없다고 했는데, 제목도 없었던 거죠.

정리 — 그래서 소스마다 파일을 나눴어요

처음엔 파싱 코드를 하나로 합치려고 했어요. "어차피 다 공고 목록인데" 싶었거든요.

금방 포기했습니다. 공통점이 거의 없었어요. 형식도, 목록 위치도, 필드 이름도, 심지어 "없을 때 어떻게 주는지"까지 달랐으니까요.

그래서 방향을 바꿨어요. 소스 하나에 파일 하나. 각자 자기 API의 이상한 점을 자기 파일 안에서 처리하고, 밖으로는 똑같은 모양으로 내보내게 했죠. 제목, 링크, 기관, 기간, 지역. 이 다섯 가지로 통일해서요.

이렇게 하니 새 소스를 붙일 때 다른 파일을 안 건드려도 됩니다. 나중에 알림 채널을 늘리거나 필터를 얹을 때도 이 구조가 계속 도움이 됐어요.

다음 편

구조를 다 맞췄더니 이번엔 같은 공고가 매시간 오기 시작했어요. 봇이 처음 본 것과 이미 본 것을 구분하지 못하면 벌어지는 일. → 5편 — 같은 공고가 계속 왔습니다


(이전 편: 3편 — 목록에 없는 API를 직접 찾아냈습니다)

댓글