| name | terminal-swarm |
| description | Agent Swarm을 위한 headless 터미널 세션 관리. 포커스 이동 없이 터미널 생성/입력/출력 제어. |
| allowed-tools | Bash |
Terminal Swarm — Claude Code Instructions
스킬 실행 시 자동 처리 (필수)
사용자가 이 스킬을 호출하면, 아래 순서를 자동으로 수행한다:
- Python 경로 확인:
~/.swarm/config.json에서 python_path 읽기. 없으면 python을 사용
1-1. 의존성 확인: python -c "import winpty, winotify, websockets, pyte, watchdog" 로 필수 패키지 설치 여부 확인. 미설치 시 pip install pywinpty winotify websockets pyte watchdog 실행
watchdog는 Files 탭 실시간 워처(OS 네이티브 ReadDirectoryChangesW)에 사용된다. 없으면 데몬은 정상 실행되지만 /files/watch SSE가 503을 반환하고 Files 탭은 수동 ↻ 새로고침에만 의존한다.
- SWARM 변수 설정: 위에서 확인한 python 경로로
SWARM 변수 설정
- Hooks 설정 확인:
$SWARM hooks status로 Claude Code hooks 설정 여부 확인. 미설정 시 $SWARM hooks setup 실행. 반드시 데몬 시작 전에 실행하여, 프리셋 복원으로 생성되는 세션에도 hooks가 적용되도록 한다.
- 데몬 상태 점검:
$SWARM status로 데몬 실행 여부 확인
- 데몬 시작 (미실행 시):
$SWARM start를 run_in_background: true로 실행, 이후 status로 정상 기동 확인
- 세션 목록 확인:
$SWARM list로 현재 실행 중인 세션 파악
- 대시보드 안내: 사용자에게
http://localhost:7890/ 에서 대시보드를 확인할 수 있다고 알림. 첫 사용자에게는 Quick Launch의 claude 항목을 클릭하면 Claude Code 세션이 바로 생성된다고 안내
데몬이 이미 실행 중이면 5는 건너뛴다.
SWARM 변수 설정
_swarm_py="$(ls ~/.claude/plugins/cache/*/terminal-swarm/*/skills/terminal-swarm/scripts/swarm.py 2>/dev/null | tail -1)"
[ -z "$_swarm_py" ] && _swarm_py=".claude/skills/terminal-swarm/scripts/swarm.py"
SWARM="$(cat ~/.swarm/config.json 2>/dev/null | python -c "import sys,json;print(json.load(sys.stdin).get('python_path','python'))" 2>/dev/null || echo python) $_swarm_py"
config가 없으면 $SWARM config init로 초기화.
⚠️ 매 Bash 호출마다 새 쉘이 생성되므로 $SWARM은 휘발된다. $SWARM을
쓰는 모든 Bash 호출의 첫 줄에 위 3줄 선언 블록을 다시 포함하거나,
여러 swarm 명령을 && / 줄바꿈으로 한 호출 안에 묶어서 실행한다.
실패 징후: read: '-n': not a valid identifier 또는
create: command not found — 이는 $SWARM이 빈 문자열로 치환되어
뒤 토큰(read, create 등)이 bash 내장/외부 명령어로 해석된 결과다.
SWARM 재선언이 빠졌다는 뜻이므로 해당 Bash 호출 맨 앞에 선언 블록을
추가해 재실행한다.
주요 기능
wait --ready 버퍼 idle fallback (1.6.11+)
wait --ready가 hook 도달에만 의존하던 구조를 이중화. Stop hook이 세션 매핑에
실패하거나 대시보드가 ack로 ready 상태를 이미 해제한 경우에도 hang 없이 복귀.
- 경로 1 (기존): hook이
ui_state="ready"로 세팅 → 즉시 반환
(source: "hook")
- 경로 2 (신규 fallback): wait 시작 후 버퍼에 한 번이라도 새 출력이 쌓였고,
마지막 출력 이후 2.5초 동안 조용하면 ready로 간주
(
source: "buffer-fallback")
attention 상태(permission 프롬프트)는 경로 1과 동일하게 즉시 반환
- 반환 JSON의
source 필드로 어느 경로였는지 식별 가능
주의: fallback은 "출력이 2.5초 멈추면 응답 종료"라는 heuristic이라, 토큰
스트리밍이 길게 쉬어가는 케이스는 조기 ready로 판정될 수 있다. Claude Code는
스트리밍이 빠르므로 실사용에서 2.5초 공백은 거의 항상 응답 완료 지점이다.
PTY/wait_for 동시성 최적화 (1.6.9+)
다중 세션·대량 출력 환경에서 세션 reader와 wait_for가 서로를 블로킹하던 두 경로를
개선.
1) pyte.Stream.feed를 별도 lock으로 분리
- 기존: PTY 리더 스레드가
_lock을 잡은 채 _vt_stream.feed(data)와 alt-screen
스냅샷까지 처리. Claude Code TUI의 전체 redraw처럼 수 KB ANSI 덩어리가 오면
pyte 상태 머신 비용만큼 같은 lock을 대기하는 get_new_raw_lines /
read_output / wait_for가 전부 멈춤.
- 수정:
Session._vt_lock을 추가해 pyte 관련 상태(_vt_stream, _vt_screen,
_vt_history)를 버퍼 lock과 분리. read_output의 alt-screen 분기와 resize도
_vt_lock 아래로 이동. WS bridge와 HTTP reader가 TUI redraw에 물리지 않는다.
2) wait_for가 이벤트 드리븐으로 전환
- 기존:
ready 모드 500ms, 일반 모드 300ms time.sleep() 폴링 루프. 오케스트
레이터가 N개 세션에 wait --ready를 동시에 걸면 N개 스레드가 계속 폴링.
- 수정:
register_data_callback으로 threading.Event를 등록해 PTY 리더가 새
데이터를 append할 때마다 즉시 깨어남 (WebSocket bridge와 동일 메커니즘).
polling CPU가 거의 0이 되고 데이터 도착 → 깨어남 latency가 폴링 주기에 종속
되지 않는다. idle/exit 감지 정확도를 위해 최대 대기는 ready 모드 0.5s, idle
모드 idle/2, 일반 모드 1s로 제한.
Editor pane dirty-dot 누락 수정 (1.6.8+)
여러 editor pane 사용 중 노란색 dirty-dot 아이콘과 저장 버튼이 아예 뜨지 않거나,
편집 후 한참 지나 사라지던 문제를 수정.
원인 3가지:
updateEditorDirty가 마지막 매칭 pane만 갱신: forEachLeaf 콜백에서
node = n로 덮어쓰기만 해, 같은 파일이 2개 pane에 열려 있거나 프리셋 복원
중 transient하게 중복된 상태에서 엉뚱한 pane의 dirty-dot이 갱신됐다.
- rebuildUI 후 dirty 상태가 새 DOM에 재동기화 안 됨:
rebuildUI로 pane
HTML이 재생성되면 .dirty-dot의 show 클래스가 날아가는데, attachEditorToPane
이 ec.dirty 상태를 새 DOM에 다시 반영하지 않아 사용자가 이미 편집한 상태에도
dot이 안 보였다.
attachEditorToPane의 동시성 race: await fetch(...) 중 rebuildUI가
재진입하면 !ec.cm 체크를 둘 다 통과해 같은 wrap 안에 CM 인스턴스 2개가
생성되고, 두 번째가 첫 번째를 덮어써 orphan CM의 on('changes')는 영영
발화 안 했다.
해결:
updateEditorDirty가 forEachLeaf로 모든 매칭 pane을 순회하며 갱신.
node.el null safety 추가.
attachEditorToPane이 attach 직후 updateEditorDirty(node.filePath)를 한 번
호출해 현재 ec.dirty 상태를 새 DOM에 재동기화.
ec._loading 플래그로 동시 진입을 차단. 두 번째 attachEditorToPane 호출은
fetch/CM 생성 블록을 건너뛰고 dirty 재동기화만 수행.
on('changes') 콜백이 node.filePath 대신 ec.filePath를 사용 — closure로
캡처된 node 객체가 rebuild로 무효화돼도 안전.
Hook Microsoft Store Python 스텁 회피 (1.6.7+)
Windows Git Bash 환경에서 1.6.6 hook 명령어가 조용히 실패하던 회귀 수정.
원인: Windows 11이 기본 설치하는 C:\Users\<u>\AppData\Local\Microsoft\WindowsApps\python3.exe는
Microsoft Store 런처 스텁이며, command -v python3이 이 경로를 "성공" 반환한다.
이 스텁은 -c "" 같은 trivial 호출에도 GUI를 띄우려다 exit 49로 죽는다.
1.6.6의 단순 fallback 체인(python3 || python)은 첫 번째가 "성공"이라 두 번째 후보가
실행되지 않아 hook이 조용히 실패했다.
수정: 두 가드 조합으로 후보 검증:
- (A)
case "$P" in *WindowsApps*) continue;; esac — Store 스텁 경로 즉시 차단
- (B)
"$P" -c "" 2>/dev/null || continue — 이름은 정상이지만 실제론 깨진 인터프리터 방어
- 후보 순서:
python3 → python → py (Windows Python Launcher)
- 셋 다 실패해도
exit 0으로 silent (hook 실패가 Claude Code 동작을 막지 않게).
hooks status의 stale 감지가 자동으로 1.6.6 → 1.6.7 마이그레이션을 트리거한다 —
사용자는 BAT 런처를 다시 클릭하기만 하면 settings.json이 새 형식으로 갱신된다.
Hook stale path 복구 (1.6.6+)
플러그인 업데이트(예: 1.5.9 → 1.6.6) 후 ~/.claude/settings.json에 박혀 있던
terminal-swarm/<old-version>/.../hook_relay.py 절대 경로가 stale 상태가 되어
hook이 조용히 실패하던 문제를 수정.
_build_swarm_hook_command가 더 이상 절대 경로를 settings.json에 박지 않는다.
hook_relay.py와 동일한 동작을 수행하는 한 줄 Python을 인라인으로 임베드한다.
플러그인 버전과 무관하게 영구 동작.
python3 우선, python fallback: bash에서 PATH 기반으로 해석 (Windows
python.exe 절대 경로의 백슬래시 이스케이프 문제도 동시에 회피).
hooks status가 stale을 자동 감지: 등록된 swarm hook command가 현재 기대
형식과 다르면 exit 1을 반환. BAT 런처의 hooks status → hooks setup 흐름이
outdated 인스톨을 자동 재설정한다.
- 부수적으로
_find_hook_relay()의 lexical sort 버그("1.5.9" > "1.5.12")도
사라졌다 — 함수 자체를 삭제했기 때문.
WebSocket 연결 안정성 (1.6.5+)
장시간 작업/대량 출력 도중 web UI pane의 키보드 입력이 멎는 zombie 연결 문제를 해결.
- 서버 → 클라이언트 application-level 하트비트: 15초마다 NUL 바이트(
\x00) 전송. xterm.js가 NUL을 무시하므로 시각적 영향 없음. 클라이언트가 lastActivity를 갱신하여 stale 감지 기준으로 사용한다.
- 클라이언트 stale 감지: 5초 주기로
lastActivity 검사. 40초간 메시지가 없으면 강제 ws.close() → onclose → reconnect. onclose가 영영 안 오는 진성 zombie를 위해 5초 fallback 타이머도 둔다.
- bridge 스레드 누수 제거:
_ws_handler가 asyncio.to_thread(threading.Event.wait) 대신 PTY 리더 스레드에서 loop.call_soon_threadsafe(data_event.set)로 직접 통신. 기존에는 disconnect마다 1개 스레드가 누수되어 ~32회 reconnect 후 default ThreadPoolExecutor가 고갈되었다.
pty.write 이벤트 루프 블로킹 방지: session.write_stdin()을 asyncio.to_thread로 위임하여, 한 pane의 PTY 블로킹이 다른 모든 pane의 WebSocket을 멎게 하지 않게 한다.
- reconnect race 가드:
handleClose가 두 번 발화하거나 force-close와 자연 close가 race할 때 reconnect가 중복 트리거되지 않도록 c.ws !== ws 식별자 가드 추가.
보안 강화 (1.6.1+)
- DNS rebinding 방어: 모든 HTTP/WebSocket 엔드포인트가
Host 헤더 화이트리스트(localhost:7890, 127.0.0.1:7890, [::1]:7890)를 검증. 미일치 시 403/1008 응답.
- CORS origin 제한:
Access-Control-Allow-Origin: * → http://localhost:7890.
- 경로 화이트리스트:
/files/tree, /files/read, /files/write는 os.getcwd() 하위 경로만 허용. 위반 시 403.
- Command 이스케이프:
Session.start()의 bash/powershell/cmd 셸 경로 전부 command injection 방지 이스케이프 적용.
- SSE 안정성:
_handle_sse에 15초 heartbeat(: ping) 추가로 스레드풀 고갈 방지. /files/watch 구독자 수 상한(32) 적용.
- 클라이언트 exponential backoff: Files 탭 EventSource 재연결이 고정 3초 → 3/6/12/24/48/60s로 증가. 성공 수신 시 리셋.
beforeunload에서 cleanup.
Files 탭 실시간 워처 (1.6.0+)
OS 네이티브 파일시스템 이벤트(ReadDirectoryChangesW)를 watchdog로 구독하여
Files 탭이 /files/watch SSE를 통해 수동 새로고침 없이 자동 갱신된다.
- 200ms 서버측 디바운스 + 300ms 클라이언트 코얼레싱으로
git pull 같은 대량 변경도 안전하게 처리
- 이벤트는 캐시된(화면에 표시된) 디렉토리만 재조회 → 보이지 않는 경로는 CPU/네트워크 소모 없음
.git/index, .git/HEAD, .git/refs/* 변경은 git status 갱신 트리거
- 무시 대상:
.git, node_modules, __pycache__, venv, .venv, dist, build, .next, .cache, .turbo, .parcel-cache + config.json의 tree_ignore
- SSE 연결이 끊기면 3초 후 자동 재연결, 15초 heartbeat(
: ping)로 프록시 타임아웃 방지
- watchdog 미설치 시
/files/watch는 503 반환, 대시보드는 수동 새로고침으로 fallback
Standalone 앱 윈도우 실행 (1.6.0+)
create_bat.py가 생성한 배치파일은 대시보드를 기본 브라우저 대신
Edge/Chrome의 --app= 모드로 띄워 chromeless 윈도우처럼 보이게 한다.
- 탐지 순서: Edge(64/86) → Chrome(64/86) → 기본 브라우저 fallback
- 자체 user-data-dir(
%LOCALAPPDATA%\TerminalSwarm\WebApp)를 지정하여
작업표시줄 아이콘과 쿠키/세션을 일반 브라우저 프로필과 분리
- 창 크기 기본값: 1400x900
세션 삭제 flicker 방지 (1.6.0+)
✕ 버튼 클릭 시 프론트엔드가 세션을 즉시 숨기면서 백그라운드 DELETE를 보내는데,
DELETE 응답 전에 폴링(/sessions)이 끼어들면 세션이 잠깐 되살아나는 race가 있었다.
_pendingDelete Set으로 가드를 추가하여 DELETE 응답 후 1.5초까지 폴링을 필터링한다.
명령어 레퍼런스
아래 모든 예제는 $SWARM이 선언된 쉘에서 실행된다고 가정한다.
실제 Bash 호출마다 위 SWARM 변수 설정 3줄을
맨 앞에 다시 포함하거나, 여러 명령을 한 호출로 묶어라.
$SWARM start
$SWARM status
$SWARM stop
$SWARM create <name> -c "cmd" -d /path
$SWARM list
$SWARM rename <old> <new>
$SWARM kill <name>
$SWARM kill-all
$SWARM send <name> <text...>
$SWARM send <name> -c "/resume"
$SWARM send <name> --enter
$SWARM send <name> --key up
$SWARM send <name> --key down --repeat 3
$SWARM send <name> -r -c "raw text"
$SWARM send <name> -f /path/to/file
echo "text" | $SWARM send <name> -b
$SWARM read <name> -n 20
$SWARM read <name> -g "ERROR"
$SWARM read <name> -r
$SWARM wait <name> --ready
$SWARM wait <name> --ready -t 600
$SWARM wait <name> --exit
$SWARM wait <name> -g "DONE"
$SWARM wait <name> -i 10
$SWARM fav list
$SWARM fav add <name> <command> [-d cwd]
$SWARM fav del <name>
$SWARM fav launch <name>
$SWARM hooks setup|status|remove
$SWARM config show|init|set|get
Claude Code 세션 제어 규칙
permission 프롬프트 응답
중요: 선택지 개수가 2개일 때와 3개일 때가 다르다. 반드시 read로 프롬프트 내용을 먼저 확인한 후 응답해야 한다.
선택지 2개 (Yes / No)
$SWARM send <name> --enter
$SWARM send <name> --key down
$SWARM send <name> --enter
선택지 3개 (Yes / Yes, don't ask again / No)
$SWARM send <name> --enter
$SWARM send <name> --key down
$SWARM send <name> --enter
$SWARM send <name> --key down --repeat 2
$SWARM send <name> --enter
주의: 2개 선택지에서 --key down --repeat 2를 보내면 의도치 않은 동작이 발생한다. 항상 read로 선택지 개수를 확인한 후 올바른 키 입력을 보내라.
장문 전송
임시 파일(Write) 생성 금지. heredoc 파이프를 사용한다.
cat <<'EOF' | $SWARM send <name> --base64
전송할 내용
EOF
에이전트 간 통신 규칙
오케스트레이터가 다른 Claude Code 세션을 제어할 때 반드시 따르는 규칙.
채널 분리
| 대상 | 방법 | 내용 |
|---|
| 사용자 | 텍스트 출력 | 상태 보고, 결과 요약 |
| 다른 세션 | send 명령 | 처리할 지시/데이터만 |
금지: 진행 보고("~를 기다리겠습니다")를 다른 세션에 send하지 마라.
메시지 전송 규칙
- 장문은 heredoc 파이프로 — 임시 파일 생성 금지
- send 후 별도 --enter 불필요 — base64 모드는 자동
\r 추가
- 한 번에 하나의 지시만 — 여러 지시를 한꺼번에 보내면 혼란
응답 대기 패턴
cat <<'EOF' | $SWARM send worker -b
작업 지시 내용
EOF
$SWARM wait worker --ready -t 120
$SWARM send worker --enter
$SWARM read worker -n 30
금지: send 직후 read 없이 바로 다음 send를 보내지 마라.
오케스트레이터 올바른 흐름
1. 사용자에게: "worker-1에게 작업을 지시합니다."
2. send: 작업 내용만 전송 (보고 문구 제외)
3. wait: 응답 대기
4. read: 결과 확인
5. 사용자에게: "worker-1 작업 완료. 결과: ..."
Agent Swarm 패턴
$SWARM create worker-1 -c "claude -p 'Fix bug #1'" -d /path/to/repo
$SWARM create worker-2 -c "claude -p 'Add tests'" -d /path/to/repo
$SWARM wait worker-1 --ready
$SWARM wait worker-2 --ready
BAT 바로가기 생성 (필수 규칙)
"bat", "배치파일", "바로가기", "런처" 등의 키워드가 포함된 요청은 반드시 이 섹션의 규칙을 따른다.
절대로 자체적으로 bat 파일을 작성하지 마라. 반드시 create_bat.py를 사용한다.
절차
Step 1: 사용자에게 두 가지를 질문한다 (반드시 물어봐야 함, 추측 금지)
- BAT 파일을 어디에 만들까요? (예:
C:\Users\me\Desktop\Terminal Swarm.bat)
- 작업 디렉토리는 어디로 할까요? (예:
C:\Users\me\projects) — 데몬이 이 경로에서 실행됩니다.
Step 2: 사용자가 두 경로를 모두 알려주면, create_bat.py를 실행한다
_create_bat="$(ls ~/.claude/plugins/cache/*/terminal-swarm/*/skills/terminal-swarm/scripts/create_bat.py 2>/dev/null | tail -1)"
[ -z "$_create_bat" ] && _create_bat=".claude/skills/terminal-swarm/scripts/create_bat.py"
python "$_create_bat" "<bat_path>" "<work_dir>"
금지사항
- 직접 bat 파일 내용을 작성하지 마라 — 반드시
create_bat.py만 사용
- 경로를 추측하지 마라 — 반드시 사용자에게 물어본다
- ~/bin, PATH, swarm.bat 등 CLI 래퍼를 만들지 마라 — 이 기능은 데몬 전체 초기화를 포함한 런처 BAT 생성 전용이다
Error Handling
| 에러 | 조치 |
|---|
Daemon is not running | $SWARM start (백그라운드) |
Session already exists | $SWARM kill <name> 후 재생성 |
Session not found | $SWARM list로 확인 |
Failed to send | $SWARM read <name>으로 종료 원인 확인 |