npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@leessju/discord-orchestrator

v0.1.6

Published

Discord 위에서 **Claude Code**와 **Codex CLI** 세션을 멀티유저 / 멀티채널로 오케스트레이션하는 봇.

Downloads

217

Readme

discord-orchestrator

Discord 위에서 Claude CodeCodex CLI 세션을 멀티유저 / 멀티채널로 오케스트레이션하는 봇.

스레드 또는 채널 = 세션 단위. 슬래시 명령으로 시작 · 재개 · 취소 · 권한 관리 · A→B 작업 지시.


핵심 기능

  • 멀티유저 채널 모드 — 한 채널에 여러 명이 봇과 동시에 대화. composeSessionKey로 thread/channel 분리, channel: prefix로 PK 충돌 방지
  • 4-tier 권한owner / member / guest / public / blocked (thread > channel > global 우선순위 + blocked override)
  • per-(user, channel) 입장 제어 — 한 유저가 채널 슬롯 독점 못 함 (기본 active 2 / queued 5)
  • A→B 작업 지시/task 슬래시 + omc_chains 2계층 (envelope 흐름 + human task_status)
  • Bystander default-deny — @mention/봇 author/reply 메시지만 prompt 포함 (privacy)
  • Approval privacy — 채널 출처 approval embed은 owner DM으로 라우팅
  • OAuth 자동 갱신 — token-refresh-daemon이 5분 주기 만료 체크 + 4시간 전부터 자동 refresh, atomic temp+rename, exponential backoff, max-failure circuit
  • Keychain 격리CLAUDE_CONFIG_DIR로 봇 자체 credentials 디렉토리 사용 (사용자 claude /login이 봇에 영향 없음)
  • stderr 401 분류 — 인증 실패 자동 감지 → daemon 깨우기 + auth_failures_total 카운터 + Discord 알림
  • healthz 관측성127.0.0.1:7707/healthz에서 token / queue / active session 상태 노출

0. 사전 요구사항

node --version   # v22+
which claude     # /opt/homebrew/bin/claude 또는 ~/.local/bin/claude
which pm2        # /opt/homebrew/bin/pm2 등

없으면:

# Node 22 (mise/nvm 권장) 또는 brew install node@22
npm install -g pm2
# claude CLI: https://docs.claude.com/en/docs/claude-code
# codex CLI (선택): https://github.com/openai/codex

지원 OS: macOS (Apple Silicon 권장) 또는 Linux


1. 클론 + 빌드 + 테스트

git clone https://github.com/leessju/discord-orchestrator.git
cd discord-orchestrator
npm install
npm run build
npm test    # 345 unit tests 통과 확인

2. Discord 봇 만들기

  1. https://discord.com/developers/applications → "New Application" → 봇 이름 입력
  2. 좌측 "Bot" 탭 → "Add Bot" → 봇 토큰 복사 (한 번만 보임, 분실 시 재생성)
  3. "Privileged Gateway Intents"MESSAGE CONTENT INTENT 활성화 (필수)
  4. 좌측 "OAuth2 → URL Generator":
    • Scopes: bot, applications.commands
    • Bot Permissions:
      • Send Messages
      • Send Messages in Threads
      • Create Public Threads
      • Embed Links
      • Attach Files
      • Use Slash Commands
      • Read Message History
  5. 생성된 URL을 브라우저에서 열어 본인 서버에 봇 초대

3. ID 수집

Discord 클라이언트 → 설정 → 고급 → 개발자 모드 ON

| 변수 | 수집 방법 | |---|---| | DISCORD_GUILD_ID | 서버 우클릭 → "ID 복사" | | OMC_OWNER_ROLE_ID | 서버 설정 → 역할 → owner 역할 우클릭 → "ID 복사" | | OMC_OWNER_USER_ID | 본인 닉네임 우클릭 → "ID 복사" (approval DM 용) | | OMC_ALERTS_CHANNEL_ID (선택) | 알림 채널 우클릭 → "ID 복사" |

owner 역할이 없으면 먼저 만들고 본인에게 부여하세요.


4. .env 작성

cp .env.example .env

.env 편집:

# Discord (필수)
DISCORD_BOT_TOKEN=<2단계에서 복사한 봇 토큰>
DISCORD_GUILD_ID=<서버 ID>
OMC_OWNER_ROLE_ID=<owner 역할 ID>
OMC_OWNER_USER_ID=<본인 user ID>
OMC_ALERTS_CHANNEL_ID=<알림 채널 ID>      # 선택

# Claude credentials 격리 (필수)
CLAUDE_CONFIG_DIR=/Users/<your-user>/.claude-bot

# 동시 세션 캡 (기본값)
OMC_MAX_ACTIVE_CLIS=6
OMC_MAX_QUEUED_SESSIONS=20
OMC_PER_USER_MAX_ACTIVE=2
OMC_PER_USER_MAX_QUEUED=5

# 채널 모드 동작 (필수)
OMC_CHANNEL_OWNER_ONLY=true
OMC_CHANNEL_INCLUDE_BYSTANDERS=false
OMC_CHANNEL_HISTORY_N=10

# 선택 — Cloudflare 우회 residential proxy (mac-server residential IP가 막히면)
# OAUTH_PROXY_URL=http://user:[email protected]:10001,http://user:[email protected]:10001

⚠️ ANTHROPIC_API_KEY=sk-ant-api-* 절대 설정 금지 — 정액 구독 손실 + daemon dormant 모드. sk-ant-oat로 시작하는 OAuth 토큰만 정상.


5. Claude / Codex 인증

봇은 두 CLI를 자식 프로세스로 spawn해서 사용자 본인의 구독 (Claude Pro/Max, ChatGPT Plus)을 그대로 활용합니다. API key가 아니라 OAuth 구독 모드라는 게 핵심.

5-A. Claude (필수)

Claude Code CLI는 OAuth 토큰을 macOS Keychain에 저장합니다. 봇은 PM2 프로세스라 GUI Keychain에 접근 못 하므로 격리 파일 경로($CLAUDE_CONFIG_DIR/.credentials.json)를 사용합니다.

1) 본인 Mac에서 1회 로그인

claude /login    # 브라우저 열림 → Anthropic 로그인 → 구독 OAuth

2) 격리 디렉토리로 시드

mkdir -p $HOME/.claude-bot && chmod 700 $HOME/.claude-bot

security find-generic-password -s "Claude Code-credentials" -w | \
  python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps({'claudeAiOauth': d.get('claudeAiOauth') or d}))" > $HOME/.claude-bot/.credentials.json.tmp
mv $HOME/.claude-bot/.credentials.json.tmp $HOME/.claude-bot/.credentials.json
chmod 600 $HOME/.claude-bot/.credentials.json

3) 원격 서버 (예: mac-server) 배포 시

원격 SSH 환경에선 직접 Keychain 못 읽으므로 로컬 Mac에서 dump → ssh로 atomic write:

ssh mac-server 'mkdir -p $HOME/.claude-bot && chmod 700 $HOME/.claude-bot'

security find-generic-password -s "Claude Code-credentials" -w | \
  python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps({'claudeAiOauth': d.get('claudeAiOauth') or d}))" | \
  ssh mac-server 'umask 077 && cat > $HOME/.claude-bot/.credentials.json.tmp && mv $HOME/.claude-bot/.credentials.json.tmp $HOME/.claude-bot/.credentials.json'

4) 자동 갱신

봇 내장 token-refresh-daemon이 5분 주기 만료 체크 + 4시간 전부터 자동 refresh. 사용자가 손댈 필요 없음.

curl -s 127.0.0.1:7707/healthz | jq .tokenStatus
# {
#   "hasToken": true,
#   "remainingMin": 347,
#   "daemonRunning": "running",
#   "refreshFailures": 0,
#   "authFailuresTotal": 0
# }

⚠️ 멀티 머신 RT 충돌

같은 Anthropic 계정을 로컬 Mac과 봇 머신 양쪽에서 사용하면 refresh_token 회전 충돌 발생할 수 있음.

  • 권장: 봇 머신에서만 사용 (로컬 claude /login 자제)
  • 충돌 시: 위 2단계 시드 다시 실행 + pm2 restart discord-orchestrator --update-env

5-B. Codex (선택, codex 사용 시만)

Codex CLI는 별도 OAuth (ChatGPT Plus 구독). 봇은 ~/.codex/auth.json을 spawn 환경에서 그대로 상속합니다 (Claude처럼 격리 안 함).

1) 봇 머신에서 1회 로그인

codex login    # 브라우저 → OpenAI 로그인 → 구독 OAuth

~/.codex/auth.json에 토큰 저장됨.

2) 원격 서버 배포 시

ssh mac-server 'mkdir -p $HOME/.codex'
scp ~/.codex/auth.json mac-server:~/.codex/auth.json
ssh mac-server 'chmod 600 ~/.codex/auth.json'

3) Codex 안 쓰면

/session start <name> codex 안 하면 됨. claude만 쓰는 환경에선 codex 인증 생략 가능.

5-C. 인증 확인 명령

# Claude
CLAUDE_CONFIG_DIR=$HOME/.claude-bot claude -p "say only: ok"
# 출력: ok

# Codex (설치+로그인 됐다면)
codex -p "say only: ok"

둘 다 "ok" 나오면 봇 spawn 시 정상 동작.


6. 실행

로컬 개발 (hot reload)

npm run dev

프로덕션 (PM2)

pm2 start ecosystem.config.cjs
pm2 save
pm2 logs discord-orchestrator    # Ctrl+C로 빠져나오기

재부팅 생존 (macOS LaunchAgent)

pm2 startup launchd -u $USER --hp $HOME
# 출력된 sudo 명령 그대로 실행

plutil -lint ~/Library/LaunchAgents/com.user.pm2.plist   # syntax 검증 (필수)
launchctl load -w ~/Library/LaunchAgents/com.user.pm2.plist

7. 동작 확인

# 헬스 (daemon 상태)
curl -s 127.0.0.1:7707/healthz | jq .tokenStatus
# daemonRunning: "running" 확인

# 슬래시 등록 (20개)
pm2 logs discord-orchestrator --lines 30 --nostream | grep "slash.registered"
# updated: 20 확인

Discord에서:

  1. 본인이 owner 역할 받았는지 확인
  2. 채널에서 봇 @mention 또는 /health 슬래시 → 응답 오면 OK
  3. 다른 멤버 추가는 /permission set <user> member

8. 새 멤버 운영 흐름

owner: /permission set @alice member       # 권한 부여
alice: 채널에서 봇 @mention 또는 봇 메시지에 reply
       → 첫 활성화 안내 임베드 1회 발행
alice: /session start work claude          # claude 세션 시작
alice: 채널에 평범하게 메시지 → 봇이 응답

9. 슬래시 명령 (20개)

| 명령 | 설명 | 권한 | |---|---|---| | /session start <name> <runtime> | 새 세션 시작 (claude/codex) | member+ | | /session stop | 세션 종료 | member+ | | /session list | 활성 세션 목록 | member+ | | /cancel | 진행 중 작업 취소 | member+ | | /resume | 재시작 후 세션 재개 | member+ | | /attach <thread> | 다른 스레드 attach | member+ | | /diff | 워크트리 diff 표시 | member+ | | /skill <name> | 등록된 스킬 호출 | member+ | | /show-trace <chainId> | 작업 체인 추적 | member+ | | /broadcast <text> | 모든 활성 세션에 메시지 발송 | owner | | /health | 시스템 상태 | public | | /wait | 큐 위치 확인 | member+ | | /model <name> | 세션 모델 변경 | member+ | | /cd <path> | 워크디렉토리 변경 | member+ | | /permission set <user> <tier> | 권한 부여 | owner | | /permission show <user> | 권한 조회 | member+ | | /permission list | 멤버십 목록 | owner | | /task assign | 다른 스레드에 작업 지시 | member+ | | /task status | 내 task 상태 | member+ | | /task accept|reject|done|cancel | task 상태 변경 | assignee |


10. 모니터링

# 토큰 상태
curl -s 127.0.0.1:7707/healthz | jq .tokenStatus
#   daemonRunning: "running" | "dormant" | "stopped" | false
#   remainingMin, refreshFailures, persistedFailures, authFailuresTotal

# 시스템 전체
curl -s 127.0.0.1:7707/healthz | jq .
#   activeSessions, queuedCount, oldestQueuedAgeMs, accountSlotsHealth, ...

# PM2 로그 실시간
pm2 logs discord-orchestrator

11. 트러블슈팅

| 증상 | 원인 | 해결 | |---|---|---| | 봇이 응답 안 함 | stderr 에러 가능성 | pm2 logs discord-orchestrator --lines 50 --nostream | | Run Failed: claude exited with code 1 + 로그 Not logged in | .credentials.json 없음/만료 | 5-A 2단계 시드 다시 실행 | | Run Failed + 로그 No conversation found with session ID | SQLite cliSessionId stale (인증 변경 후) | sqlite3 data/orchestrator.sqlite "UPDATE omc_sessions SET cli_session_id = NULL" | | healthz daemonRunning: "dormant" | ANTHROPIC_API_KEY=sk-ant-api-* 설정됨 | .env에서 API key 빼고 OAuth 시드 | | [token-refresh] Max failures (10) reached | RT consumed (멀티 머신 충돌) | 5-A 2단계 재시드 + restart | | [token-refresh] OAuth refresh failed: 403 | Cloudflare 차단 (residential IP) | OAUTH_PROXY_URL 설정 또는 수동 retoken (~25일 주기) | | state.read-corrupt log | .omc/state/token-refresh.json 손상 | 자동 복구됨 (counters reset to defaults) | | clock-skew.refuse-start | NTP drift >30분 | sudo sntp -sS time.apple.com 후 restart | | token.persist-disk-low 알림 | $HOME/.claude-bot FS <100MB free | logs/ 정리 + restart | | slash.registered updated:0 | 봇 토큰/Guild ID 잘못됨 | .env 재확인 | | Codex 응답 없음 | ~/.codex/auth.json 없음 | codex login 실행 | | Permission denied | owner role ID 불일치 또는 권한 없음 | owner role ID 확인 + /permission set |

자세한 운영 가이드는 RUNBOOK.md 참조.


12. 아키텍처

                            +----------------------+
                            |   Discord Guild      |
                            | thread/channel = sess |
                            +----------+-----------+
                                       |
                                       v
+--------------------------------------------------------------------+
|              discord-orchestrator (Node + PM2)                     |
|                                                                    |
|  Discord client → Skill registry → Gateway router                  |
|        ↓               ↓                ↓                          |
|  Session-key      Permission gate   Admission queue                |
|  (thread/channel) (4-tier)          (per-user/channel cap)         |
|                                          ↓                         |
|                                   Runtime adapters                 |
|                                   (Claude / Codex)                 |
|                                          ↓                         |
|              Token-refresh-daemon → spawn(claude/codex)            |
|              (5-min cycle, R5 persistence,                         |
|               wakeUpDaemon non-blocking,                           |
|               stderr 401 classifier)                               |
|                                                                    |
|  SQLite (WAL) — sessions, runs, accounts, chains, memberships      |
|  JSONL transcripts/<threadId>.jsonl                                |
|  CAS attachments by sha256                                         |
|  Outbox for inter-agent envelopes                                  |
|  Healthz on 127.0.0.1:7707                                         |
+--------------------------------------------------------------------+

13. 테스트

npm test                       # 345 unit tests (default)
npm run test:integration       # 실제 OAuth 사용 (INTEGRATION_REAL_OAUTH=1 필요)
npm run test:e2e               # 실제 Discord 봇 사용 (RUN_E2E=1 필요)

vitest config는 default npm test에서 tests/integration/**, tests/e2e/**를 자동 제외합니다.


14. 디렉토리 구조

discord/
├── src/
│   ├── index.ts                    # 부트스트랩
│   ├── discord/                    # discord.js 클라이언트, slash, render
│   ├── gateway/                    # router, queue, session-store, credential-broker
│   ├── runtime/                    # Claude/Codex adapters, account-selector
│   │   └── auth/                   # token-refresh-daemon, proxy
│   ├── inter-agent/                # envelope, bus, MCP, approval, outbox, tasks-store
│   ├── skills/                     # registry, permissions + 20 registered skills
│   └── observability/              # logger, healthz, alerts
├── tests/
│   ├── unit/                       # 345 vitest cases
│   ├── integration/                # gated by INTEGRATION_REAL_OAUTH=1
│   └── e2e/                        # gated by RUN_E2E=1
├── ecosystem.config.cjs            # PM2
└── RUNBOOK.md                      # 운영 가이드

관련 문서

  • docs/AUTH.md — OAuth 인증 deep dive (CLI v2.1.77+ 동작, RT 회전, Multi-machine 충돌, Q4 외부 write 감지, claude_adaptation Bug 1-8 정리, 모니터링 체크포인트)
  • RUNBOOK.md — 운영 / 장애 대응 / Auth resilience 설정 / Vacation Posture

라이선스

MIT (개인 프로젝트)