블로그 회사 시스템 구축기

claude -p와 텔레그램으로 음성비서 만들기: 폰에 말하면 집 맥이 답하는 구성

갤럭시 폰 앱이 받아 적은 말을 집 맥의 게이트웨이가 claude -p나 다른 에이전트에 넘기고, 결과를 텔레그램과 이어폰으로 돌려받는 구성입니다. API 키 없이 구독 CLI를 쓰는 방법과 답이 안 올 때 확인할 곳을 함께 적었습니다.

갤럭시 폰에 “헤르메스야 디스크 용량 확인해줘”라고 말하면, 집 맥이 실제로 df를 돌리고 “431GB 남음”을 텔레그램으로 보내옵니다. 그 알림은 갤럭시 버즈가 한국어로 읽어 줍니다. 이런 명령은 같은 맥에서 돌던 AI 에이전트(Hermes)가 실행하고, 보통 대화는 따로 API 키를 발급하지 않고 맥에서 이미 구독으로 쓰던 Claude Code CLI를 claude -p로 불러 답합니다.

아이폰에서 갤럭시로 넘어오면서 맥과 붙던 기능이 대부분 끊겼고, 알림·클립보드·화면은 무료 도구로 다시 붙였습니다(갤럭시와 맥을 무료 도구로 연결한 과정). 음성 명령만 마땅한 게 없어서 직접 만들었고, 아래는 그 구성과 막혔던 곳입니다.

음성비서 전체 구성: 폰, 게이트웨이, AI, 텔레그램

폰 앱이 받아 적은 말이 게이트웨이를 거쳐 claude -p 즉답 또는 에이전트 위임으로 나뉘고, 결과가 폰 음성이나 텔레그램과 버즈로 돌아오는 흐름도

구성 요소는 네 개입니다.

  • 폰 앱 · 직접 만든 안드로이드 앱입니다. 말을 글로 받아 적는 일(STT)과 답을 소리로 읽는 일(TTS)은 폰에 들어 있는 한국어 엔진을 씁니다. 무료이고 지연도 짧습니다.
  • 게이트웨이 · 24시간 켜 두는 맥미니에서 도는 작은 HTTP 서버입니다. Node 24 내장 모듈만 쓰고 외부 패키지는 없습니다. 포트는 8787이고, 폰은 Tailscale 내부 주소로 여기에 접속합니다.
  • 두뇌 · 게이트웨이가 받은 문장을 claude -p에 넘기거나, 같은 맥에서 돌던 다른 AI 에이전트(Hermes)에 넘깁니다.
  • 텔레그램 봇과 버즈 · 시간이 걸리는 일의 결과는 텔레그램으로 오고, 버즈가 그 알림을 읽어 줍니다.

폰과 맥이 서로 다른 네트워크에 있어도 되는 건 Tailscale 덕분입니다. 공유기 포트포워딩 없이 폰에서 맥 주소로 바로 붙습니다(Tailscale로 흩어진 기기를 한 네트워크로 묶은 방법).

말이 들어오면 두 갈래로 나뉩니다.

말처리답이 오는 길
보통 대화claude -p 즉답폰이 바로 읽어 줌
”헤르메스”로 시작에이전트에 넘김텔레그램 → 버즈

“헤르메스”로 시작하는 말은 게이트웨이가 에이전트의 웹훅으로 넘기고 폰에는 바로 “전달했어요”라고 답합니다. 에이전트가 df 같은 명령을 실제로 실행하고, 결과를 텔레그램으로 보냅니다. 대화는 빨리 끝나야 하고 일은 시간이 걸리니, 둘을 나눠 둔 것입니다.

API 키 없이 claude -p를 음성비서 두뇌로 쓰기

처음에는 Anthropic API를 붙이려고 했습니다. 그런데 맥미니에서 이미 claude CLI를 구독으로 쓰고 있어서, 게이트웨이가 그걸 프로세스로 부르게 바꿨습니다. API 키를 새로 만들 필요가 없고, 대화마다 따로 나가는 토큰 요금도 없습니다. 구독 안에서 처리됩니다.

게이트웨이는 Node의 spawn으로 아래와 같은 명령을 실행합니다.

cd ~/.galaxy-mac/voice-cwd        # 아무것도 없는 중립 폴더
claude -p "$MESSAGE" \
  --output-format json \
  --model claude-haiku-4-5-20251001 \
  --system-prompt-file /path/to/assistant.md \
  --allowedTools ""

모델은 기본으로 Haiku 4.5를 씁니다. 음성 대화는 답이 늦으면 바로 불편해서 빠른 모델을 골랐습니다. 그래도 CLI를 매번 새로 띄우는 방식이라 API를 직접 부를 때보다 첫 응답이 몇 초 느립니다.

페르소나 파일에는 “응답은 이어폰으로 재생된다”, “목록·표·코드블록·마크다운 기호를 쓰지 않는다”, “모르면 모른다고 한다” 같은 원칙을 적어 두었습니다. 귀로 듣는 답이라 마크다운 기호가 섞이면 그대로 읽혀 버립니다.

claude -p가 코딩 도우미처럼 답할 때: 실행 폴더를 확인

claude -p를 프로젝트 폴더에서 부르니, 음성비서로 부른 AI가 코딩 도우미처럼 답했습니다. claude는 실행한 폴더의 CLAUDE.md를 읽어 들이는데, 거기 적힌 개발 지침까지 따른 것입니다.

세 가지로 고쳤습니다.

  1. 아무 파일도 없는 중립 폴더(~/.galaxy-mac/voice-cwd)를 만들어 거기서만 실행합니다.
  2. --system-prompt-file로 비서 역할을 따로 줍니다. 공식 CLI 문서 기준으로 이 옵션은 기본 시스템 프롬프트를 파일 내용으로 통째로 바꿉니다.
  3. 도구를 쓰지 못하게 막습니다.

3번은 제 구성에 --allowedTools ""로 들어가 있는데, Claude Code CLI 문서를 다시 보니 --allowedTools는 “허락을 묻지 않고 실행할 도구” 목록이고, 쓸 수 있는 도구 자체를 빼는 옵션은 --tools ""입니다(2026년 10월 확인). 새로 만든다면 --tools ""를 쓰는 편이 의도에 맞습니다.

서버에서 띄우면 “Not logged in”이 날 때

게이트웨이를 낮에만 켜는 작업용 맥에서 24시간 켜 두는 맥미니로 옮기자, launchd나 ssh로 띄운 claude가 “Not logged in”을 냈습니다. claude CLI가 로그인 정보를 화면 로그인 세션의 키체인에 저장해서, 화면 없이 뜬 프로세스는 그걸 못 읽습니다.

해결은 claude setup-token입니다. 공식 문서에 “CI와 스크립트용 장기 OAuth 토큰을 만든다, Claude 구독이 필요하다”고 적혀 있는 명령입니다. 이 토큰을 게이트웨이의 .env에 CLAUDE_CODE_OAUTH_TOKEN으로 넣고, 게이트웨이가 claude를 띄울 때 환경 변수로 넘기게 했습니다.

같이 확인할 것이 두 가지 있었습니다.

  • 토큰은 기기마다 따로 발급합니다. 한 토큰을 여러 기기에서 같이 쓰면 갱신이 서로 부딪혀 401 오류가 납니다.
  • 옮긴 맥의 CLI 버전이 낮으면 옵션이 없을 수 있습니다. 2.1.81 버전에서 --system-prompt-file이 안 돼서, 파일 내용을 읽어 --system-prompt에 그대로 넣는 방식으로 바꿨습니다.

텔레그램 봇 답장이 안 올 때 볼 곳

에러도 없이 텔레그램 답장만 안 오는 일이 있었습니다. chat_id 자리에 봇 이름(@...)을 넣어 둔 것이 원인이었습니다.

# 잘못된 예 (조용히 실패)
chat_id = "@my_bot_name"

# 올바른 예
chat_id = "<숫자-CHAT-ID>"

Telegram Bot API 문서의 sendMessage 설명을 보면 chat_id에는 “대상 채팅의 고유 식별자, 또는 대상 봇·슈퍼그룹·채널의 @username”이 들어갑니다. 봇이 나에게 개인 메시지를 보내려면 나와 봇 사이 대화방의 숫자 ID를 넣어야 합니다. 봇 이름을 넣으면 받는 쪽이 내가 아니게 됩니다. 토큰과 chat_id 실제 값은 코드에 적지 않고 .env에만 둡니다.

chat_id가 맞는데도 안 갈 때는 네트워크를 봅니다. 게이트웨이를 옮긴 맥미니의 네트워크가 api.telegram.org 접속을 막고 있었고, 같은 맥에서 돌던 에이전트의 로그를 보고서야 알았습니다. 접속이 막히면 다른 주소로 한 번 더 보내도록 재시도를 넣었습니다.

에이전트에 넘겼는데 아무 일도 안 할 때

“헤르메스”로 시작하는 말을 에이전트에 넘기는 부분에서도 두 번 막혔는데, 둘 다 에러 없이 아무 일도 안 일어나서 원인을 찾는 데 오래 걸렸습니다.

  • 웹훅 프롬프트에 {text} 자리표시자가 없으면 내가 말한 문장이 에이전트에 전달되지 않습니다. 빈 지시로 세션만 열립니다.
  • 에이전트 도구 목록(platform_toolsets)에 webhook 항목이 없으면 도구 없는 세션이 열려서 명령을 실행하지 못합니다.

웹훅 요청에는 HMAC 서명(X-Hub-Signature-256)을 붙여, 게이트웨이가 아닌 곳에서 보낸 요청은 받지 않게 했습니다.

버즈가 텔레그램 알림을 읽어 주게 하는 설정

이 부분은 코드가 필요 없습니다. Galaxy Wearable 앱에서 “알림 읽어주기”를 켜고, 텔레그램 알림에 미리보기와 내용 표시를 켜 두면 버즈가 알림 내용까지 한국어로 읽어 줍니다. 워치는 폰 알림을 그대로 받아 보여 주니 손목에도 같이 뜹니다.

음성으로 지우는 명령은 막아 둔 이유

음성 인식은 가끔 엉뚱하게 알아듣습니다. 그 한 번이 서버 파일을 지우는 명령이 되면 곤란해서, 음성 경로는 조회만 하게 했습니다. 디스크 여유, 프로세스 상태, 백업 상태 확인 정도입니다. 삭제나 설정 변경은 음성으로 시키지 않습니다.

게이트웨이 쪽 장치는 이렇습니다.

  • 임의 명령을 실행하는 주소가 없습니다. hooks.json에 이름과 스크립트를 미리 등록해 둔 것만 실행하고, 요청에 인자를 넘길 수 없습니다.
  • 모든 요청에 X-Auth-Token 헤더가 있어야 합니다. 토큰을 정하지 않으면 게이트웨이가 아예 뜨지 않습니다.
  • Tailscale 내부망에서만 접속합니다. 공유기 포트포워딩은 하지 않습니다.
  • 즉답용 claude -p에는 허락 없이 실행할 도구를 하나도 지정하지 않았습니다. 도구 자체를 빼려면 앞에 적은 대로 --tools ""를 씁니다.

만드는 중에 음성으로 다른 맥의 설정을 바꾸게 하려던 적이 있는데, 배포 직전에 Claude Code의 안전 분류기가 그 원격 쓰기를 막았습니다. 그 부분은 제가 직접 실행했습니다.

이 구성으로 안 되는 것

  • 맥이 꺼지면 아무것도 안 됩니다. 두뇌가 맥에 있고 폰은 말을 받아 적어 보내기만 합니다. 처음에는 낮에만 켜는 작업용 맥에 두었다가, 그 맥이 꺼지면 음성비서도 같이 멈춰서 24시간 켜 두는 맥미니로 옮겼습니다.
  • 첫 답이 몇 초 걸립니다. CLI를 매번 새로 띄우기 때문입니다.
  • 긴 작업은 즉답으로 오지 않습니다. 에이전트에 넘긴 일은 끝난 뒤 텔레그램으로 따로 옵니다.
  • 조회 말고는 음성으로 시키지 않습니다. 위에 적은 대로 일부러 막아 둔 것입니다.

자주 묻는 질문

음성비서를 만드는데 별도 AI API 키가 꼭 필요한가요?

아니요. 이미 구독해서 쓰는 CLI가 있다면 그걸 로컬 프로세스로 호출하면 됩니다. 저는 claude -p를 게이트웨이에서 실행하는 방식으로 붙였고, 별도 API 키 발급 없이 구독 CLI를 그대로 재사용했습니다.

claude -p로 음성비서를 만들면 왜 코딩 도우미처럼 답하나요?

claude -p는 실행한 폴더의 CLAUDE.md를 읽어 인격을 바꾸기 때문입니다. 아무것도 없는 중립 폴더에서 실행하고, --system-prompt-file로 페르소나를 고정하면 음성비서 말투로 고정됩니다.

텔레그램으로 회신이 조용히 안 갈 때 먼저 볼 곳은 어디인가요?

chat_id 자리에 봇 이름(@...)을 넣었는지 확인하세요. 여기에는 반드시 숫자 chat_id가 들어가야 하고, 봇 이름을 넣으면 에러 없이 전송만 되지 않습니다.

#음성비서#텔레그램 봇#CLI#홈서버#1인 개발