ワンクリックで
terminal-swarm
Agent Swarm을 위한 headless 터미널 세션 관리. 포커스 이동 없이 터미널 생성/입력/출력 제어.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Agent Swarm을 위한 headless 터미널 세션 관리. 포커스 이동 없이 터미널 생성/입력/출력 제어.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | terminal-swarm |
| description | Agent Swarm을 위한 headless 터미널 세션 관리. 포커스 이동 없이 터미널 생성/입력/출력 제어. |
| allowed-tools | Bash |
사용자가 이 스킬을 호출하면, 아래 순서를 자동으로 수행한다:
~/.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 변수 설정$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.py 자동 탐색
_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가 hook 도달에만 의존하던 구조를 이중화. Stop hook이 세션 매핑에
실패하거나 대시보드가 ack로 ready 상태를 이미 해제한 경우에도 hang 없이 복귀.
ui_state="ready"로 세팅 → 즉시 반환
(source: "hook")source: "buffer-fallback")attention 상태(permission 프롬프트)는 경로 1과 동일하게 즉시 반환source 필드로 어느 경로였는지 식별 가능주의: fallback은 "출력이 2.5초 멈추면 응답 종료"라는 heuristic이라, 토큰 스트리밍이 길게 쉬어가는 케이스는 조기 ready로 판정될 수 있다. Claude Code는 스트리밍이 빠르므로 실사용에서 2.5초 공백은 거의 항상 응답 완료 지점이다.
다중 세션·대량 출력 환경에서 세션 reader와 wait_for가 서로를 블로킹하던 두 경로를
개선.
1) pyte.Stream.feed를 별도 lock으로 분리
_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 아이콘과 저장 버튼이 아예 뜨지 않거나, 편집 후 한참 지나 사라지던 문제를 수정.
원인 3가지:
updateEditorDirty가 마지막 매칭 pane만 갱신: forEachLeaf 콜백에서
node = n로 덮어쓰기만 해, 같은 파일이 2개 pane에 열려 있거나 프리셋 복원
중 transient하게 중복된 상태에서 엉뚱한 pane의 dirty-dot이 갱신됐다.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로 무효화돼도 안전.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이 조용히 실패했다.
수정: 두 가드 조합으로 후보 검증:
case "$P" in *WindowsApps*) continue;; esac — Store 스텁 경로 즉시 차단"$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이 새 형식으로 갱신된다.
플러그인 업데이트(예: 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")도
사라졌다 — 함수 자체를 삭제했기 때문.장시간 작업/대량 출력 도중 web UI pane의 키보드 입력이 멎는 zombie 연결 문제를 해결.
\x00) 전송. xterm.js가 NUL을 무시하므로 시각적 영향 없음. 클라이언트가 lastActivity를 갱신하여 stale 감지 기준으로 사용한다.lastActivity 검사. 40초간 메시지가 없으면 강제 ws.close() → onclose → reconnect. onclose가 영영 안 오는 진성 zombie를 위해 5초 fallback 타이머도 둔다._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을 멎게 하지 않게 한다.handleClose가 두 번 발화하거나 force-close와 자연 close가 race할 때 reconnect가 중복 트리거되지 않도록 c.ws !== ws 식별자 가드 추가.Host 헤더 화이트리스트(localhost:7890, 127.0.0.1:7890, [::1]:7890)를 검증. 미일치 시 403/1008 응답.Access-Control-Allow-Origin: * → http://localhost:7890./files/tree, /files/read, /files/write는 os.getcwd() 하위 경로만 허용. 위반 시 403.Session.start()의 bash/powershell/cmd 셸 경로 전부 command injection 방지 이스케이프 적용._handle_sse에 15초 heartbeat(: ping) 추가로 스레드풀 고갈 방지. /files/watch 구독자 수 상한(32) 적용.beforeunload에서 cleanup.OS 네이티브 파일시스템 이벤트(ReadDirectoryChangesW)를 watchdog로 구독하여
Files 탭이 /files/watch SSE를 통해 수동 새로고침 없이 자동 갱신된다.
git pull 같은 대량 변경도 안전하게 처리.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: ping)로 프록시 타임아웃 방지/files/watch는 503 반환, 대시보드는 수동 새로고침으로 fallbackcreate_bat.py가 생성한 배치파일은 대시보드를 기본 브라우저 대신
Edge/Chrome의 --app= 모드로 띄워 chromeless 윈도우처럼 보이게 한다.
%LOCALAPPDATA%\TerminalSwarm\WebApp)를 지정하여
작업표시줄 아이콘과 쿠키/세션을 일반 브라우저 프로필과 분리✕ 버튼 클릭 시 프론트엔드가 세션을 즉시 숨기면서 백그라운드 DELETE를 보내는데,
DELETE 응답 전에 폴링(/sessions)이 끼어들면 세션이 잠깐 되살아나는 race가 있었다.
_pendingDelete Set으로 가드를 추가하여 DELETE 응답 후 1.5초까지 폴링을 필터링한다.
아래 모든 예제는
$SWARM이 선언된 쉘에서 실행된다고 가정한다. 실제 Bash 호출마다 위 SWARM 변수 설정 3줄을 맨 앞에 다시 포함하거나, 여러 명령을 한 호출로 묶어라.
# 데몬 제어
$SWARM start # 데몬 시작 (반드시 run_in_background: true)
$SWARM status # 데몬 상태 확인
$SWARM stop # 데몬 + 모든 세션 종료
# 세션 관리
$SWARM create <name> -c "cmd" -d /path # 세션 생성 (-s 셸 지정 가능)
$SWARM list # 세션 목록
$SWARM rename <old> <new> # 이름 변경
$SWARM kill <name> # 세션 종료
$SWARM kill-all # 모든 세션 종료
# 입출력
$SWARM send <name> <text...> # 텍스트 입력 (자동 \r)
$SWARM send <name> -c "/resume" # /로 시작하는 텍스트 (MSYS 경로변환 방지)
$SWARM send <name> --enter # Enter 키 전송
$SWARM send <name> --key up # 키 전송 (up/down/ctrl-c/tab/home/end/...)
$SWARM send <name> --key down --repeat 3 # 키 반복
$SWARM send <name> -r -c "raw text" # raw 모드 (자동 \r 없음)
$SWARM send <name> -f /path/to/file # 파일 내용을 base64로 전송
echo "text" | $SWARM send <name> -b # stdin -> base64 전송
$SWARM read <name> -n 20 # 최근 N줄 읽기
$SWARM read <name> -g "ERROR" # 패턴 필터링
$SWARM read <name> -r # 헤더 없이 raw 출력
# 완료 대기 (블로킹)
$SWARM wait <name> --ready # Claude Code 응답 완료까지 대기 (hooks 기반, 기본 타임아웃 300초)
$SWARM wait <name> --ready -t 600 # ready + 타임아웃
$SWARM wait <name> --exit # 프로세스 종료까지 대기
$SWARM wait <name> -g "DONE" # 패턴 출력까지 대기
$SWARM wait <name> -i 10 # 10초 idle 대기
# Quick Launch
$SWARM fav list # 목록
$SWARM fav add <name> <command> [-d cwd] # 추가
$SWARM fav del <name> # 삭제
$SWARM fav launch <name> # 실행
# Hooks / Config
$SWARM hooks setup|status|remove # Claude Code hooks 관리
$SWARM config show|init|set|get # 설정 관리
중요: 선택지 개수가 2개일 때와 3개일 때가 다르다. 반드시 read로 프롬프트 내용을 먼저 확인한 후 응답해야 한다.
# Yes (기본 선택)
$SWARM send <name> --enter
# No
$SWARM send <name> --key down
$SWARM send <name> --enter
# Yes (기본 선택)
$SWARM send <name> --enter
# Yes, don't ask again
$SWARM send <name> --key down
$SWARM send <name> --enter
# No
$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하지 마라.
\r 추가# 1. 메시지 전송
cat <<'EOF' | $SWARM send worker -b
작업 지시 내용
EOF
# 2. 응답 대기 (★ 권장: --ready)
$SWARM wait worker --ready -t 120
# 반환 이벤트: ready / attention / exit / timeout
# 3. attention이면 permission 프롬프트 또는 질문 응답
$SWARM send worker --enter
# 4. 결과 읽기
$SWARM read worker -n 30
금지: send 직후 read 없이 바로 다음 send를 보내지 마라.
1. 사용자에게: "worker-1에게 작업을 지시합니다."
2. send: 작업 내용만 전송 (보고 문구 제외)
3. wait: 응답 대기
4. read: 결과 확인
5. 사용자에게: "worker-1 작업 완료. 결과: ..."
# 세션 생성
$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 파일을 작성하지 마라. 반드시 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>"
create_bat.py만 사용| 에러 | 조치 |
|---|---|
Daemon is not running | $SWARM start (백그라운드) |
Session already exists | $SWARM kill <name> 후 재생성 |
Session not found | $SWARM list로 확인 |
Failed to send | $SWARM read <name>으로 종료 원인 확인 |