회사 메일·캘린더·Teams를 Claude가 읽고, 중요한 건 Telegram으로 알려준다

지난번에 LLM Wiki + Telegram Bot을 만들었다. Claude Code가 개인 위키를 관리하고, Telegram으로 원격 조종하는 구조였다. 이번에는 여기에 Microsoft 365 연동을 붙였다. 회사 메일, 캘린더, Teams 대화를 자동으로 가져와서 위키에 쌓고, 중요한 건 Telegram으로 알려주는 시스템이다.

문제: 정보가 흩어져 있다

하루에 확인해야 할 채널이 너무 많다. Outlook 메일, Teams 채팅, 캘린더 일정이 각각 다른 앱에 흩어져 있다. 중요한 메시지를 놓치거나, “오늘 뭐 해야 하지?” 하고 여러 앱을 번갈아 열어보는 시간이 아깝다.

원하는 건 단순하다:

  • 메일, 캘린더, Teams를 한 곳에 모아서 보고
  • 중요한 건 알아서 알려주고
  • 나머지는 나중에 찾을 수 있게 쌓아두기

해결: mogcli + Claude + Telegram

mogcli라는 오픈소스 CLI 도구를 발견했다. Microsoft Graph API를 래핑해서, 터미널에서 메일/캘린더/Teams를 조작할 수 있다. 이걸 5분마다 자동 실행하고, Claude가 중요도를 판단해서 Telegram으로 알려주는 파이프라인을 만들었다.

전체 구조

systemd timer (5분 주기)
    |
    v
m365-sync.sh (bash 스크립트)
    |
    +-- mogcli: 메일, 캘린더 가져오기
    +-- Graph API: Teams 대화 가져오기
    |
    v
raw/m365/ (JSON 원본 축적)
    |
    +-- latest-mail.json      (항상 최신)
    +-- latest-calendar.json
    +-- latest-teams.json
    +-- 2026-04-16/           (일별 히스토리)
    |       mail-1043.json
    |       calendar-1043.json
    |       teams-1043.json
    |
    v
새 메시지 감지 (이전 스냅샷과 비교)
    |
    v
claude -p --model haiku (중요도 판단)
    |
    +-- 중요하면 --> Telegram 알림
    +-- 아니면 --> 조용히 넘어감
    |
    v
Claude가 위키에 ingest (CLAUDE.md 규칙에 따라)

1단계: mogcli 설치

mogcli는 Go로 작성되어 있어서 go install로 바로 설치할 수 있다.

# Go 설치 (mise 사용)
mise use -g go@latest

# mogcli 설치
go install github.com/jaredpalmer/mogcli/cmd/mog@latest

2단계: Microsoft Entra 앱 등록

mogcli가 회사 Microsoft 365에 접근하려면 Entra(구 Azure AD)에 앱을 등록해야 한다.

  1. entra.microsoft.com에서 App registrations > New registration
  2. Authentication 탭에서:
    • Mobile and desktop applications 플랫폼 추가
    • https://login.microsoftonline.com/common/oauth2/nativeclient 체크
    • Settings 탭에서 Allow public client flows > Yes
  3. API permissions에서 Microsoft Graph Delegated 권한 추가:
    • Mail.Read, Calendars.Read, Contacts.Read, Tasks.Read, User.Read
    • Teams용: Chat.Read, ChannelMessage.Read.All
  4. Grant admin consent 클릭 (Teams 권한에 필요)

3단계: 인증

mog auth
# Application (client) ID 입력
# Tenant ID 입력
# 브라우저에서 디바이스 코드 인증 완료

인증이 완료되면 바로 사용할 수 있다:

# 메일 가져오기
mog mail list --max 20 --json

# 이번 주 캘린더
mog calendar list --from 2026-04-14 --to 2026-04-20 --json

Teams는 mogcli에서 아직 지원하지 않아서, mogcli의 토큰을 꺼내서 Graph API를 직접 호출한다:

TOKEN=$(cat ~/.config/mogcli/keyring/... | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")

curl -s -H "Authorization: Bearer $TOKEN" \
  "https://graph.microsoft.com/v1.0/me/chats?\$top=10&\$expand=lastMessagePreview"

4단계: 5분마다 자동 수집

bash 스크립트를 만들어서 systemd user timer로 5분마다 실행한다.

# systemd service
# ~/.config/systemd/user/m365-sync.service
[Unit]
Description=Microsoft 365 Sync

[Service]
Type=oneshot
ExecStart=/home/user/llmwiki/bot/m365-sync.sh
# systemd timer
# ~/.config/systemd/user/m365-sync.timer
[Unit]
Description=Microsoft 365 Sync every 5 minutes

[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
Persistent=true

[Install]
WantedBy=timers.target
# 활성화
systemctl --user daemon-reload
systemctl --user enable --now m365-sync.timer

스크립트는 매번:

  1. 메일, 캘린더, Teams 원본 JSON을 raw/m365/에 축적
  2. latest-*.json으로 최신 스냅샷 유지
  3. 이전 스냅샷과 비교하여 새 메시지 감지
  4. 내가 보낸 메시지, 뉴스레터, GitLab 알림 등은 자동 필터링

5단계: Claude가 중요도 판단

여기가 핵심이다. 키워드 매칭이 아니라, Claude가 내용을 읽고 판단한다.

# 새 메시지를 Claude에게 파이프
echo "$NEW_MESSAGES" | claude -p --model haiku \
  "이 메시지들 중 즉시 확인해야 할 중요한 것이 있는지 판단하라.
   중요하면 Telegram 알림 메시지를 작성하라.
   출처를 명시하라 (메일/Teams).
   중요한 게 없으면 NONE만 출력하라."

Claude(haiku 모델)가 빠르게 판단해서:

  • 팀원의 업무 요청, 장애 보고, 미팅 변경 등은 알림
  • 광고, 뉴스레터, 단순 정보 공유는 무시

알림이 결정되면 Telegram Bot API로 발송한다. 각 메시지에 출처(메일/Teams)가 명시되어 있어서, 어디서 온 건지 바로 알 수 있다.

6단계: Claude가 위키에 정리

축적된 raw JSON은 Claude가 위키에 정리한다. CLAUDE.md에 ingest 규칙을 정의해두면, Claude가 대화 중에 자연스럽게 데이터를 읽고 일간 노트에 정리한다:

  • 오늘 일정을 타임라인으로 표시
  • 중요 메일만 필터링하여 목록
  • Teams 대화에서 액션 아이템 추출
  • 할 일 목록 업데이트

결과

이제 아침에 Telegram만 확인하면 된다. 중요한 메일이나 Teams 메시지가 오면 Claude가 알아서 판단해서 알려준다. Outlook이나 Teams 앱을 열지 않아도, 놓치는 게 없다.

raw 데이터는 날짜별로 쌓이니까, 나중에 “지난주에 누가 뭐라고 했더라?” 같은 질문에도 Claude가 검색해서 답해줄 수 있다.

지난번 LLM Wiki + Telegram Bot이 “내가 Claude에게 물어보는” 구조였다면, 이번에는 “Claude가 먼저 알려주는” 구조가 추가된 셈이다. 수동 질의에서 능동 알림으로.

사용한 도구

  • mogcli – Microsoft Graph CLI (Go, MIT)
  • Claude Code – CLI에서 claude -p로 파이프 분석
  • systemd user timer – 5분 주기 실행
  • Telegram Bot API – 알림 발송
  • Microsoft Entra – 앱 등록 및 OAuth 인증

나의 첫 하네스 — Telegram으로 LLM Wiki 원격 조종하기

오늘 하루 만에 개인 지식 베이스를 만들고, 그걸 Telegram으로 원격 조종하는 시스템을 만들었다. 돌이켜보면 이것이 나의 첫 하네스 엔지니어링이었다.

시작: Karpathy의 LLM Wiki

모든 것은 Andrej Karpathy의 LLM Wiki 패턴에서 시작했다. 핵심 아이디어는 간단하다 — RAG처럼 매번 원본에서 검색하는 대신, LLM이 위키를 점진적으로 구축하고 유지관리하게 만드는 것이다.

구조는 3계층이다:

  • Raw sources (raw/) — 원본 자료. 불변.
  • Wiki (wangsyObsidian/) — LLM이 유지관리하는 마크다운 파일들. Obsidian vault.
  • Schema (CLAUDE.md) — LLM에게 “이렇게 일해라”를 알려주는 규칙서.

Claude Code를 열고 Karpathy Gist를 공유하면서 시작했다. Claude가 기존 Obsidian vault 구조를 분석하고, CLAUDE.md 스키마를 작성하고, index.mdlog.md를 생성하고, 기존 42개 페이지를 색인했다. 한 시간도 안 걸렸다.

문제: 이 컴퓨터 앞에 앉아 있어야 한다

위키가 돌아가기 시작하니까 바로 문제가 보였다. 원격에서는 쓸 수가 없다.

출퇴근길에 흥미로운 기사를 발견해도, 회의 중에 메모할 것이 생겨도, 집 컴퓨터 앞에 앉아서 Claude Code를 열어야만 ingest할 수 있다. 지식 베이스는 24시간인데 입력 통로는 컴퓨터 앞에 있는 시간만큼만 열려 있는 셈이다.

내가 원한 것은 세 가지였다:

  1. 어디서든 URL이나 텍스트를 보내면 즉시 ingest
  2. 어디서든 질문하면 위키 기반으로 답변
  3. 이미 매일 쓰고 있는 앱에서

답은 Telegram Bot이었다.

아키텍처: 봇은 얇게, 두뇌는 Claude에게

설계 원칙을 하나 잡았다: 봇은 메시지를 중계할 뿐, 모든 지식 작업은 Claude Code가 한다.

스마트폰 → Telegram → Bot 서버(내 PC) → Claude Code CLI → Wiki

봇의 역할은 정말 단순하다:

  1. Telegram 메시지를 받는다
  2. claude -p로 Claude Code에 전달한다
  3. 결과를 Telegram으로 돌려보낸다

위키의 모든 규칙(어디에 저장할지, 어떻게 교차참조할지, index를 어떻게 업데이트할지)은 CLAUDE.md에 있고, Claude Code가 그대로 따른다. 봇 코드에는 위키 로직이 한 줄도 없다.

이게 왜 중요한가? 하네스 엔지니어링의 핵심 원칙과 연결된다:

“에이전트가 실수하면 에이전트가 아니라 하네스를 고쳐라”

봇이 위키 로직을 갖고 있으면, 규칙을 바꿀 때 봇 코드와 CLAUDE.md 두 곳을 고쳐야 한다. 봇을 얇게 유지하면 CLAUDE.md만 고치면 된다. 하네스(CLAUDE.md)가 단일 진실의 근원이 된다.

구현: 생각보다 간단했다

Telegram Bot 생성

BotFather에게 /newbot — 30초면 끝난다. 토큰을 받는다.

봇 코드 (Python, 약 100줄)

핵심은 run_claude 함수 하나다:

async def run_claude(prompt: str) -> str:
    proc = await asyncio.create_subprocess_exec(
        "claude", "-p",
        "--allowedTools", "Read,Write,Edit,Glob,Grep,WebFetch,...",
        cwd=str(WIKI_DIR),
        stdin=asyncio.subprocess.PIPE,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE,
    )
    stdout, stderr = await proc.communicate(input=prompt.encode())
    return stdout.decode().strip()

claude -p는 Claude Code의 비대화형 모드다. stdin으로 프롬프트를 넘기면 stdout으로 결과가 나온다. --allowedTools로 필요한 도구(파일 읽기/쓰기, 웹 가져오기)를 허용한다.

나머지는 Telegram 핸들러를 붙이는 것 뿐:

  • URL이 오면 → “이 URL을 ingest해줘”
  • 텍스트가 오면 → “위키에서 이 질문에 답해줘”
  • 파일이 오면 → raw/에 저장 → “이 파일을 ingest해줘”
  • /lint 명령 → “위키를 lint해줘”

systemd로 상시 운영

.service 파일 하나 만들고 systemctl enable --now — PC가 켜져 있는 한 봇은 항상 살아 있다.

실사용 흐름

출퇴근길에 기사 발견:
Telegram에 URL 붙여넣기 → 봇이 자동 ingest → raw/에 원본 보존 → Atlas/에 요약 페이지 생성 → index.md 업데이트 → log.md 기록

회의 중 떠오른 질문:
Telegram에 “지난 프로젝트 설정이 어떻게 됐더라?” 입력 → 봇이 위키 검색 → 답변 도착

누군가 전달한 정보 기록:
Telegram에 /ingest 김과장이 내일 미팅에서 예산안 가져온다고 함raw/에 보존 → 위키에 통합

삽질 기록

순탄하지만은 않았다. 기록해둔다.

  1. --prompt 옵션은 없다claude --print --prompt "..." 가 아니라 claude -p "..." 이다. prompt는 위치 인수.
  2. --allowedTools와 위치 인수 충돌claude -p --allowedTools "..." "prompt" 에서 prompt가 인식 안 됨. stdin 파이프로 해결.
  3. 비대화형 모드에서 도구 권한-p 모드는 도구 승인 프롬프트를 띄울 수 없다. --allowedTools로 사전 허용 필수.

이런 삽질들이 모두 하네스를 이해하는 과정이었다. 에이전트(Claude)의 문제가 아니라 하네스(호출 방식, 권한 설정)의 문제였고, 하네스를 고치니 해결됐다.

이것이 하네스 엔지니어링인가?

돌이켜보면, 오늘 한 것은 정확히 하네스 엔지니어링이었다:

  • Schema 설계CLAUDE.md에 위키의 규칙과 워크플로우를 정의
  • 입출력 채널 설계 — Telegram Bot으로 원격 접근 경로 구축
  • 도구 권한 설계--allowedTools로 에이전트가 쓸 수 있는 도구 범위 지정
  • 얇은 중계층 — 봇은 로직 없이 메시지만 전달, 모든 판단은 에이전트가

긱뉴스에서 읽었던 그 문장이 떠오른다:

“남이 만든 하네스를 그대로 복사해 쓰는 것은 남의 옷을 입는 것과 비슷하다”

Karpathy의 LLM Wiki 구조를 가져왔지만 그대로 쓰지 않았다. 내 Obsidian vault 구조에 맞추고, Calendar를 raw source로 재정의하고, Telegram 입력 채널을 추가했다. 남의 패턴을 가져와서 내 것으로 만드는 과정 — 그것이 하네스 엔지니어링의 시작이었다.

다음 단계

  • 이미지 ingest 지원 (Telegram 사진 메시지)
  • 긴 응답의 마크다운 포맷팅 개선
  • Obsidian URI 연동 (응답에서 바로 페이지 열기)
  • 주기적 자동 lint

하루 만에 여기까지 온 것이 믿기지 않는다. 대부분의 코드는 Claude가 짰고, 나는 방향을 잡고 결정을 내렸다. 이것도 하네스 엔지니어링의 일부인 것 같다 — 에이전트가 일하는 구조를 설계하는 것이 곧 에이전트와 함께 일하는 방식이니까.