모든 글
약 4분 분량 학습 프로젝트/텔레그램 봇

폰에서 Claude를 돌리려고 텔레그램 봇을 만들었다

목차

폰에서 Claude에게 일을 시키고 싶었습니다. 자리에 앉아 있을 때는 노트북에서 Claude Code를 쓰면 되는데, 밖에 있을 때 떠오른 걸 바로 넘길 방법이 없었습니다. “아까 그 아이디어 코드로 만들어놔” 같은 걸 폰으로 던져두고 나중에 확인하고 싶었습니다. 그래서 텔레그램 메시지를 받으면 서버에서 Claude를 돌리고 답을 돌려주는 봇을 만들었습니다.

구독 CLI로 돌린 이유

가장 먼저 정한 건 과금 방식이었습니다. Claude를 프로그램에서 부르는 방법은 두 가지입니다. API 키를 발급받아 토큰만큼 따로 내는 길과 이미 결제하고 있는 Claude Code 구독을 그대로 쓰는 길입니다. 저는 매달 구독료를 내고 있었으니 그 안에서 해결하고 싶었습니다.

방법은 claude -p였습니다. Claude Code에는 대화 화면 없이 명령 한 줄로 질문을 넣고 답만 받는 모드가 있습니다. 여기에 claude setup-token으로 발급한 장기 인증 토큰을 붙이면 서버에서도 구독 인증이 그대로 통합니다. 봇이 하는 일은 텔레그램에서 받은 문장을 이 명령에 넘기고 나온 출력을 다시 텔레그램으로 보내는 것입니다. API 키는 한 번도 쓰지 않았고 추가 과금도 없습니다.

디스코드 봇이 있는데 왜 텔레그램인가

저는 이미 디스코드 봇을 하나 돌리고 있습니다. 알고리즘 스터디의 출석을 화면 공유 시간으로 재는 봇입니다. 그쪽은 서버와 채널, 음성방, 멤버가 있는 단체 공간을 전제로 돌아갑니다. 이번에 필요한 건 반대쪽이었습니다. 저 혼자 봇 하나와 일대일로 이야기하는 개인 비서에 가까웠습니다. 텔레그램에서 봇과 나누는 일대일 대화가 이 모양에 그대로 맞았습니다.

두 번째 이유는 폰에서 넣는 입력의 종류였습니다. 긴 지시를 걸으면서 타이핑하기는 번거로운데, 텔레그램 음성 메시지는 봇이 오디오 파일로 받아 그대로 변환기에 넘길 수 있습니다. 마이크를 누르고 “auth 부분 리팩터링해줘”라고 말하면 텍스트로 바뀌어 Claude에게 갑니다. 사진이나 파일도 같은 방식으로 붙어서 에러 화면을 찍어 보내고 원인을 물어보는 흐름이 자연스러웠습니다.

세 번째는 네트워크였습니다. 텔레그램 봇은 롱 폴링으로 돕니다. 봇이 텔레그램 서버 쪽으로 먼저 접속해 새 메시지를 가져오는 구조라, 내 서버에 포트를 열거나 공인 도메인을 붙일 필요가 없습니다. 방화벽 안쪽에 그대로 두고 돌릴 수 있었습니다. 디스코드로도 비슷하게 만들 수는 있었지만 혼자 쓰는 비서에는 텔레그램 쪽이 더 짧은 길이었습니다.

작업 과정을 보여줘야 했다

처음 만든 버전은 최종 답변만 돌려줬습니다. 짧은 질문에는 괜찮았는데, 코딩을 시키면 문제가 생겼습니다. 파일 몇 개를 고치고 명령을 여러 번 돌리는 동안 텔레그램에는 아무것도 뜨지 않다가, 다 끝나고 나서야 결과가 한 번에 나왔습니다. 몇 분간 화면이 조용하면 봇이 멈춘 건지 일하는 건지 알 수가 없었습니다.

그래서 출력 방식을 스트리밍으로 바꿨습니다. Claude Code에는 작업을 진행하면서 무슨 도구를 쓰는지 한 줄씩 흘려주는 모드가 있습니다. 봇은 그중 파일 작성이나 명령 실행 같은 신호를 골라 사람이 읽을 문장으로 바꿔 보냅니다. “파일 작성: app.py”, “명령 실행: 테스트 실행”이 실시간으로 갱신되고 작업이 끝나면 그 자리에 최종 답변이 옵니다. 지금 뭘 하고 있는지 보이니까 기다리는 게 견딜 만해졌습니다.

확인을 건너뛴 대가와 취소 장치

코딩 모드에는 함정이 하나 있었습니다. 노트북의 Claude Code는 파일을 고치기 전에 “이거 해도 될까요”라고 물어보고 승인을 기다립니다. 텔레그램에는 그 승인 버튼을 띄울 자리가 없어서 물어보게 두면 봇이 거기서 멈춰버립니다. 그래서 코딩 모드는 확인 없이 바로 실행하는 설정과 한 세트입니다. 편한 대신, 잘못 보낸 메시지 하나가 그대로 실행된다는 뜻이기도 합니다.

이 문제를 두 겹으로 막았습니다. 코딩 명령을 받으면 바로 시작하지 않고 몇 초간 취소 버튼을 띄웁니다. 잘못 보낸 걸 알아챌 시간입니다. 이미 시작한 뒤에도 진행 상황 아래에 중단 버튼이 붙습니다. 누르면 실행 중인 Claude 프로세스를 끊습니다. 여기에 더해 파괴적인 명령은 실행 직전에 자동으로 거부하도록 훅을 걸었습니다. 홈 디렉터리를 지우거나 서버를 재부팅하는 명령이 오면 확인 없이 실행하는 모드여도 훅의 거부가 이깁니다.

남의 서비스가 도는 서버라는 조건

이 봇은 제 다른 프로젝트가 이미 돌고 있는 서버에 얹었습니다. 여윳돈으로 새 서버를 띄우는 대신 놀고 있던 자리를 쓴 셈인데, 그 서버에는 도커로 여러 서비스가 함께 살고 있었습니다. 코딩 모드의 봇은 사실상 서버에서 명령을 실행할 수 있으니, 실수든 잘못된 지시든 옆 서비스의 데이터를 건드릴 여지가 있었습니다.

봇이 그 서비스들의 폴더에는 아예 접근하지 못하도록 격리부터 걸었습니다. systemd에는 특정 경로가 그 서비스에는 존재하지 않는 것처럼 감추는 설정이 있습니다. 이걸 걸어두면 봇이 높은 권한으로 우회를 시도해도 그 경로만은 보이지 않습니다. 봇은 자기 작업 폴더 안에서만 움직이고 옆에서 돌던 서비스들은 그대로 두었습니다.

지금 상태와 한계

지금은 개인 규모입니다. 쓰는 사람은 저 하나이고 허용한 텔레그램 계정 외에는 봇이 응답하지 않습니다. 배포는 코드를 고쳐 올리면 깃허브 액션이 서버에 반영하고 서비스를 재시작하는 데까지 자동으로 이어집니다. 서버를 옮기게 될 때를 대비해 지금까지 손으로 한 설치 과정도 스크립트 하나로 재현되게 정리해 두었습니다.

한계도 분명합니다. 봇이 답하려면 서버가 켜져 있어야 하고 구독 사용량은 노트북의 Claude Code와 한 통을 나눠 쓰기 때문에 한도를 넘기면 봇도 같이 멈춥니다. 메시지마다 무거운 모델을 돌리면 사용량이 빨리 닳아서 평소에는 가벼운 설정으로 두고 어려운 작업에만 강도를 올리는 식으로 씁니다. 그래도 밖에서 떠오른 걸 폰으로 던져두고 이따 확인하는 원래 목적에는 잘 맞습니다.

Author
작성자 @범수

오늘의 노력이 내일의 전문성을 만든다고 믿습니다.

댓글

댓글 수정/삭제는 GitHub Discussions에서 가능합니다.