NanoClaw Skills 기반 Telegram 전환 및 Fork 정리

· 8분 읽기
목차

add-telegram skill 개선, fork custom 구조 확립, 브랜치 정리, upstream 동기화까지의 전체 흐름.

배경

NanoClaw의 Telegram 기능을 수동 코드에서 skills engine 기반으로 전환하고, upstream과의 관계를 정리했다. 핵심 판단은 “NanoClaw 철학(개인 노트북 어시스턴트)에 맞는 것만 upstream에, 나머지는 fork custom으로 관리”이다.

3계층 코드 구조 확립

flowchart TD
    UP["Upstream Base<br/>원본 NanoClaw"] --> SK["Skills 적용<br/>.claude/skills/ → apply-skill.ts"]
    SK --> FK["Fork Customs<br/>origin 레벨 관리"]

    UP ---|"/update-nanoclaw<br/>skill로 동기화"| SK
    SK ---|"재적용 시<br/>fork custom 덮어씌워짐"| FK
계층내용관리 방법
Upstream baseNanoClaw 원본 코드git merge upstream/main 또는 /update-nanoclaw
Skillsadd-telegram 등 skill 패키지apply-skill.ts로 적용, .nanoclaw/state.yaml에 기록
Fork customswebhook, sendImage, CLAUDE_MODEL 등origin/main에서 수동 관리

add-telegram Skill 개선

기존 skill 템플릿 대비 추가된 기능:

기능설명
Webhook + Polling 듀얼모드TELEGRAM_WEBHOOK_URL 유무로 자동 분기
MarkdownV2 3단계 fallbackMarkdownV2 → HTML → plain text 순차 시도
telegramify-markdown마크다운을 Telegram MarkdownV2로 변환
sendImageInputFile 기반 이미지 전송 + IPC 연동
429 retryTelegram API rate limit 시 retry_after만큼 대기 후 재시도

철학적 판단: Webhook은 Fork Custom

upstream skill 템플릿은 polling-only가 NanoClaw 철학에 부합한다.

항목PollingWebhook
설정토큰만 넣으면 끝공인 URL, 포트, 방화벽/터널 필요
환경노트북, 가정 WiFi, NAT 뒤 OK공인 IP 또는 터널 필수
대상개인 어시스턴트프로덕션 서비스

webhook 관련 코드(connectWebhook, TELEGRAM_WEBHOOK_URL, WEBHOOK_PORT)는 fork custom으로만 유지.

Fork Custom 전체 목록

기능파일
Telegram webhook 모드telegram.ts
sendImage + IPCtelegram.ts, index.ts
MarkdownV2 3단계 fallbacktelegram.ts
429 retrytelegram.ts
CLAUDE_MODEL opus 고정config.ts, index.ts, task-scheduler.ts
logger.warn 수정index.ts
scheduler-deduptask-scheduler.ts
tool-use 로깅agent-runner/index.ts
lint-staged + .nvmrc개발환경
페르소나 설정각종 설정

Upstream PR 정리

PR결과비고
CJK 폰트 PRMerged유일한 upstream 기여
tool-use 로깅 PRClosed (직접)fork custom으로 유지
scheduler-dedup PRClosed (직접)fork custom으로 유지
host-commands PRClosed (직접)skills 철학에 안 맞음

add-telegram skill 개선 PR은 미진행 — webhook/sendImage/IPC가 얽혀 polling-only 분리 비용 대비 가치 낮음.

Upstream PR 규칙 (private 전환 후)

origin repo를 private으로 전환하면서 GitHub fork 관계가 영구 분리되었다. public으로 되돌려도 fork 라벨은 복원되지 않는다 (GitHub 정책).

private로 유지하는 이유:

  • .env 패턴, 도메인, 인프라 구성 등이 커밋 히스토리에 포함
  • fork custom 코드가 개인 인프라 구조를 드러냄

upstream PR 절차:

  1. PR 전용 public fork 생성
  2. private repo에서 해당 변경만 cherry-pick하여 clean 브랜치 생성
  3. public fork에 push → upstream PR 생성
  4. 개인 인프라 코드가 섞이지 않도록 주의

브랜치 정리

모든 feature 브랜치 삭제. main만 남김:

삭제: improve/add-telegram-skill (local + origin)
삭제: feat/cjk-fonts (origin, upstream 머지 완료)
삭제: fix/scheduler-dedup (origin, main에 이미 반영)
삭제: feat/tool-use-logging (origin, main에 이미 반영)
삭제: feat/host-commands-upstream (local + origin, PR closed)

Upstream 동기화

git merge upstream/main으로 11커밋 수용:

주요 변경영향
third-party model supportANTHROPIC_BASE_URL/AUTH_TOKEN 추가
/update/update-nanoclawskills-engine 대폭 간소화, 구 update 삭제
WhatsApp 메시지 정규화whatsapp.ts 수정

충돌 없이 자동 머지 완료. 전체 392 테스트 통과.

개발환경 개선

항목BeforeAfter
Pre-commit hooknpm run format:fix (전체 포맷팅 → 잔여 변경)npx lint-staged (staged만 포맷팅 + re-stage)
Node 버전명시 없음.nvmrc로 Node 22 고정
CLAUDE.md기본 정보만Work Rules 섹션 추가
Auto memory없음프로젝트별 메모리 파일 구축

Git Hooks & Formatter 설정

커밋 시 staged .ts 파일만 자동 포맷팅되도록 Husky + lint-staged를 구성했다.

문제: 기존 pre-commit hook은 src/**/*.ts 전체를 포맷팅해서, 커밋 후 unstaged 변경이 남는 문제가 있었다. staged 파일만 포맷팅하고 자동 re-stage하도록 변경.

flowchart LR
    GC["git commit"] --> HK["Husky<br/>pre-commit hook"]
    HK --> LS["lint-staged<br/>staged 파일 필터"]
    LS --> PR["Prettier<br/>포맷팅 + re-stage"]
    PR --> CM["커밋 완료<br/>잔여 변경 없음"]

관련 devDependencies:

패키지버전역할
husky^9.1.7Git hooks 관리
lint-staged^16.3.1staged 파일만 lint/format
prettier^3.8.1코드 포맷터

Node 22 vs 25 이슈

서비스(launchd)는 Node 22, 터미널 기본은 Node 25. better-sqlite3 C++ 네이티브 모듈은 Node 버전 불일치 시 ERR_DLOPEN_FAILED 발생. npm install, npm rebuild, 테스트 모두 Node 22 환경에서 실행해야 한다.

# Node 22 환경으로 전환 후 작업
export NVM_DIR="$HOME/.nvm" && . "$NVM_DIR/nvm.sh" && nvm use 22

# better-sqlite3 리빌드 (Node 버전 변경 후 필수)
npm rebuild better-sqlite3

최종 상태

flowchart LR
    subgraph Git
        M["main<br/>v1.1.6"]
        O["origin/main<br/>private"]
        U["upstream/main<br/>v1.1.6"]
        M --- O
        U -.->|"git fetch upstream<br/>/update-nanoclaw"| M
    end

    subgraph "PR 전용 (필요 시 생성)"
        PF["upstream PR용<br/>public fork"]
        PF -.->|"cherry-pick → PR"| U
    end

    subgraph Service
        S["NanoClaw 서비스<br/>Telegram 연동"]
    end
  • 브랜치: main만 존재
  • origin: private (fork 관계 영구 분리)
  • upstream 동기화: git fetch upstream / /update-nanoclaw 정상 동작
  • upstream PR: 별도 public fork 생성하여 제출

관련 글