Update Multi-AI CLI doc with Windows local implementation details
- Add pywinauto/pyperclip/watchdog dependencies - Include code examples for window control and log monitoring - Add PySimpleGUI sample UI code - Document PowerShell Start-Transcript and WSL tee logging
This commit is contained in:
parent
7e8ca31115
commit
df93ac07fa
@ -1,273 +1,437 @@
|
|||||||
---
|
---
|
||||||
date: 2025-09-29
|
date: 2025-09-29
|
||||||
author: happybell80
|
author: happybell80
|
||||||
tags: [ai, cli, integration, automation]
|
tags: [ai, cli, integration, automation, windows, pywinauto, local]
|
||||||
status: idea
|
status: idea (local-pivot)
|
||||||
---
|
---
|
||||||
|
|
||||||
# Multi-AI CLI 통합 시스템 구상
|
# Multi-AI CLI 통합 시스템 구상 (로컬/Windows 전환)
|
||||||
|
|
||||||
## 현황
|
## 개요
|
||||||
- **사용 중인 AI CLI**: Claude (23/24 서버), Gemini CLI, OpenAI Codex CLI (로컬)
|
- 목적: 웹/서버/프록시 없이, Windows 로컬 환경에서 다수의 터미널 창(SSH/WSL/로컬)을 그대로 활용해 Claude/Gemini/OpenAI(CLI·스크립트)에게 동시에 질문하고, 응답을 수집·비교·요약
|
||||||
- **인증 방식**: 브라우저 세션 기반 (쿠키/토큰 사용, API 미사용)
|
- 전제: 사용자가 각 터미널 창에서 세션(인증·쿠키·토큰)을 수동으로 유지하며, 로그인/재인증은 사람이 직접 처리
|
||||||
- **현재 워크플로우**: 수동으로 각 CLI에 질문하고 답변 종합
|
- 접근: Windows GUI 자동화(pywinauto) + 로그 tail 기반 수집으로 안정 제어. 화면 스크래핑 의존 최소화(클립보드 충돌 방지)
|
||||||
|
|
||||||
### CLI 설치 현황
|
## 현황 (로컬 중심)
|
||||||
- **로컬**: Claude, Gemini 설치 (OpenAI 없음)
|
- 사용 중: PowerShell/Windows Terminal/WSL 창 5개 이상 동시 실행, 일부는 SSH로 51123/51124 서버 접속 후 Claude 실행
|
||||||
- **51123 서버**: Claude만 설치
|
- 인증: 전부 수동(사용자). 앱은 인증을 대행하지 않음
|
||||||
- **51124 서버**: Claude, Gemini, OpenAI 모두 설치, 세션 파일 접근 가능
|
- 과거 웹/프록시/FastAPI 계획은 폐기. 본 문서는 “로컬 GUI 자동화” 기반으로 전면 전환
|
||||||
|
|
||||||
## 구현 완료 (2025-09-29)
|
|
||||||
|
|
||||||
### 핵심 해결책: 비-TTY 파이프 모드
|
|
||||||
- **문제**: pexpect가 PTY 생성 → TUI 감지 → 대화형 모드 실행
|
|
||||||
- **해결**: subprocess.exec + 파이프 + 환경변수로 TUI 회피
|
|
||||||
- **검증**: 모든 CLI에서 non-interactive 모드 작동 확인
|
|
||||||
|
|
||||||
### CLI별 구현 방법 (최종 검증)
|
|
||||||
- **Claude**: `claude --print "prompt"` → 텍스트 출력 (--output-format json은 --verbose 필요)
|
|
||||||
- **Gemini**: `gemini -p "prompt"` → stdout 스트리밍 (서버에 .gemini/settings.json 설정 필요)
|
|
||||||
- **OpenAI**: Mock 구현 (대화형 CLI 없음, API 전용)
|
|
||||||
|
|
||||||
### 환경변수 설정
|
|
||||||
```python
|
|
||||||
env = {
|
|
||||||
'CI': '1', # CI 환경 표시
|
|
||||||
'TERM': 'dumb', # 터미널 기능 없음
|
|
||||||
'NO_COLOR': '1', # ANSI 컬러 비활성화
|
|
||||||
'CLICOLOR': '0' # 추가 컬러 비활성화
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 배포 현황
|
|
||||||
- **51124 서버**: 8888 포트에서 FastAPI 서버 실행 중
|
|
||||||
- **51123 서버**: nginx /multi-ai/ → 51124:8888 프록시
|
|
||||||
- **접속 URL**: https://ro-being.com/multi-ai/
|
|
||||||
- **상태**: ✅ 모든 어댑터 정상 작동, WebSocket 연결 활성화
|
|
||||||
|
|
||||||
## 목표
|
## 목표
|
||||||
여러 AI의 의견을 동시에 얻어 더 나은 의사결정을 내리고 개발 생산성 향상
|
- 단일 입력창에서 다중 터미널에 질문을 브로드캐스트
|
||||||
|
- 각 창의 응답을 안정적으로 수집(로그 tail 권장)하여 패널별로 스트리밍 표시
|
||||||
|
- 응답 비교/요약, 재전송/타임아웃/실패 처리, 대화 이력(JSONL/선택적 SQLite) 제공
|
||||||
|
|
||||||
## 구현 방안 우선순위
|
## 로컬 디렉토리 구조(제안)
|
||||||
|
|
||||||
### 1. tmux + bash
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# multi-ai.sh
|
|
||||||
tmux new-session -d -s ai-panel
|
|
||||||
tmux split-window -h -t ai-panel
|
|
||||||
tmux split-window -v -t ai-panel
|
|
||||||
tmux send-keys -t ai-panel:0.0 "claude" Enter
|
|
||||||
tmux send-keys -t ai-panel:0.1 "gemini" Enter
|
|
||||||
tmux send-keys -t ai-panel:0.2 "openai" Enter
|
|
||||||
tmux attach -t ai-panel
|
|
||||||
```
|
```
|
||||||
- 장점: 구현 간단, 즉시 사용 가능
|
multi-ai-local/
|
||||||
- 단점: 수동 입력 필요, 응답 통합 어려움
|
├── app.py # 로컬 GUI(단일 입력창, 응답패널, 브로드캐스트/선택 전송)
|
||||||
|
├── adapters/
|
||||||
### 2. Python 래퍼
|
│ ├── base.py # WindowAdapter 인터페이스(창 찾기/포커스/전송)
|
||||||
- subprocess.Popen으로 각 CLI 프로세스 제어
|
│ ├── claude_win.py # Claude 창 어댑터(제목 규칙, 로그 경로)
|
||||||
- asyncio.gather로 동시 질의 및 응답 수집
|
│ ├── gemini_win.py # Gemini 창 어댑터
|
||||||
- 세션 파일 경로: ~/.config/claude, ~/.config/gemini
|
│ └── openai_win.py # OpenAI CLI/스크립트 어댑터
|
||||||
- stdin/stdout 스트림 비동기 처리
|
├── logs/ # 각 창의 Transcript/tee 로그 (앱이 tail)
|
||||||
|
├── config.yaml # 창 제목/모델/로그경로/프롬프트패턴/타임아웃 매핑
|
||||||
### 3. FastAPI 웹 대시보드
|
└── pyproject.toml # pywinauto, pyperclip, watchdog, (옵션) PySimpleGUI/PySide6
|
||||||
- WebSocket으로 실시간 스트리밍 응답 처리
|
|
||||||
- 3열 레이아웃으로 동시 응답 표시
|
|
||||||
- 대화 이력 SQLite/LMDB 저장
|
|
||||||
|
|
||||||
**주요 기능**:
|
|
||||||
- 단일 입력창으로 모든 AI에 동시 질의
|
|
||||||
- 실시간 응답 스트리밍 (3열 레이아웃)
|
|
||||||
- 대화 이력 저장/검색
|
|
||||||
- 응답 비교 및 최선 선택
|
|
||||||
- 세션 관리 (쿠키/토큰 경로 공유)
|
|
||||||
|
|
||||||
### 4. 기대 효과
|
|
||||||
|
|
||||||
#### 시나리오 1: 코드 리뷰
|
|
||||||
- **입력**: "이 함수의 문제점과 개선 방안"
|
|
||||||
- **Claude**: 버그 및 엣지케이스 분석
|
|
||||||
- **Gemini**: 성능 최적화 제안
|
|
||||||
- **OpenAI**: 리팩토링 패턴 추천
|
|
||||||
- **결과**: 다각도 검토로 코드 품질 향상
|
|
||||||
|
|
||||||
#### 시나리오 2: 장애 대응
|
|
||||||
- **입력**: "서버 응답 지연 원인 분석"
|
|
||||||
- **Claude**: 로그 패턴 분석
|
|
||||||
- **Gemini**: 시스템 리소스 진단
|
|
||||||
- **OpenAI**: 즉시 적용 가능한 해결책
|
|
||||||
- **결과**: 신속한 문제 해결
|
|
||||||
|
|
||||||
#### 시나리오 3: 아키텍처 설계
|
|
||||||
- **입력**: "마이크로서비스 분리 전략"
|
|
||||||
- **Claude**: 도메인 경계 분석
|
|
||||||
- **Gemini**: 기술 스택 제안
|
|
||||||
- **OpenAI**: 마이그레이션 로드맵
|
|
||||||
- **결과**: 균형잡힌 설계 결정
|
|
||||||
|
|
||||||
## 구현 로드맵
|
|
||||||
|
|
||||||
### 개발 단계
|
|
||||||
1. **Phase 0**: 단일 CLI PoC - Claude만으로 PTY/파싱 검증
|
|
||||||
2. **Phase 1**: tmux 스크립트 작성 및 테스트
|
|
||||||
3. **Phase 2**: Python pexpect 래퍼 개발 및 확장
|
|
||||||
4. **Phase 3**: FastAPI 백엔드 구축
|
|
||||||
5. **Phase 4**: 웹 UI 및 실시간 통신
|
|
||||||
6. **Phase 5**: 세션 공유, 응답 분석 기능
|
|
||||||
|
|
||||||
### 산출물
|
|
||||||
- **PoC 스크립트**: 단일 CLI 제어 검증 코드
|
|
||||||
- **재현 스크립트**: 각종 에러 상황 시뮬레이션
|
|
||||||
- **골든 로그**: raw/clean 출력 샘플
|
|
||||||
- **파서 규칙 문서**: ANSI 제거, 프롬프트 패턴
|
|
||||||
- **계약 테스트**: 어댑터 인터페이스 검증
|
|
||||||
|
|
||||||
## 배포 방식
|
|
||||||
**UV 가상환경 사용** (Docker 대신)
|
|
||||||
- 세션 파일(~/.config/claude, ~/.config/gemini) 직접 접근
|
|
||||||
- 브라우저 인증 자연스럽게 연동
|
|
||||||
- tmux는 pipx로 설치
|
|
||||||
- FastAPI는 uv로 관리
|
|
||||||
|
|
||||||
## PoC 개발 계획
|
|
||||||
|
|
||||||
### Phase 0: 단일 CLI 프로토타입
|
|
||||||
- **대상**: Claude CLI만으로 시작
|
|
||||||
- **기술**: subprocess.exec (파이프 모드) - PTY 회피로 TUI 방지
|
|
||||||
- **검증 시나리오**:
|
|
||||||
- 정상 스트리밍 응답
|
|
||||||
- 세션 만료 처리
|
|
||||||
- Rate limit 대응
|
|
||||||
- 느리거나 중단된 스트림
|
|
||||||
- 프롬프트 미복귀 상황
|
|
||||||
|
|
||||||
### 메시지 스키마 (JSONL)
|
|
||||||
```
|
|
||||||
{"type": "request", "model": "claude", "prompt": "...", "timestamp": "..."}
|
|
||||||
{"type": "chunk", "model": "claude", "content": "...", "timestamp": "..."}
|
|
||||||
{"type": "final", "model": "claude", "content": "...", "latency": 1.23}
|
|
||||||
{"type": "error", "model": "claude", "error": "session_expired", "timestamp": "..."}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### CLI 어댑터 계약
|
## 핵심 아이디어: 창은 사람처럼 “운전”, 출력은 로그로 “수집”
|
||||||
- **인터페이스**: healthcheck() | ask()->async iterator | cancel() | resume()
|
- 입력(전송)
|
||||||
- **모드**: PTY/STDIO 모드 지원
|
- 질문을 클립보드에 복사
|
||||||
- **정규화**: ANSI 제거, 프롬프트 재출현, 타임아웃 규칙 내장
|
- 대상 창 활성화 → Ctrl+V → Enter
|
||||||
- **CLI 플래그** (2025-09-29 검증):
|
- 포커스 실패/지연은 재시도(백오프) 및 사용자 알림
|
||||||
- Claude: `-p "prompt" --output-format json` (result 필드 추출)
|
- 출력(수집)
|
||||||
- Gemini: `-p "prompt"` (stdout 스트리밍)
|
- 각 창에서 `Start-Transcript`(PowerShell) 또는 `tee -a`로 파일 로그 기록
|
||||||
- OpenAI: `chat.completions.create --stream` (SSE 파싱)
|
- 앱은 watchdog으로 로그를 tail하며 라인/청크 단위로 이벤트(JSONL) 생성
|
||||||
|
- 화면 복사(Ctrl+A→Ctrl+C)는 보조 수단(클립보드 경합 가능성)
|
||||||
|
- 정규화/종료 판단
|
||||||
|
- ANSI/박스문자 제거, 코드블록 fence 보정
|
||||||
|
- 종료: 침묵 타임아웃 + 프롬프트 패턴(예: `>`, `$`, `~$`, `C:\>`)
|
||||||
|
|
||||||
### 파싱 규칙
|
## 동작 시나리오 (최소 5창 + 입력창)
|
||||||
- **종료 판단**: 프롬프트 패턴 + 침묵 타임아웃 + 길이 상한
|
1. PowerShell: `ssh admin@51123` 접속 → Claude 실행
|
||||||
- **정규화**: 코드블록/표 정상화, fence 언어 보정
|
2. PowerShell: `ssh admin@51124` 접속 → Claude 실행
|
||||||
- **골든 로그**: raw/clean 출력 비교로 파서 규칙 확정
|
3. Windows Terminal/WSL: Gemini CLI 실행(세션 유지)
|
||||||
|
4. Windows Terminal/WSL: OpenAI CLI/스크립트 실행(스트리밍 옵션 권장)
|
||||||
|
5. 로컬 PowerShell/WSL: 보조 CLI 또는 로그 뷰어
|
||||||
|
6. 앱 하단: 단일 입력창(브로드캐스트/선택 전송 지원)
|
||||||
|
|
||||||
### 에러 복구 전략
|
흐름
|
||||||
- **분류**: Auth/Rate/Parse/Timeout
|
1) 입력창에 “질문” 작성 → 다중 대상 선택 → 전송
|
||||||
- **복구**: 지수 백오프, 재시도, 세션 만료시 수동 로그인 안내
|
2) 각 창은 해당 CLI에 입력 전달되고 실행
|
||||||
|
3) 로그 tail로 응답을 수집→패널 스트리밍 표출, 완료/오류 이벤트 발생
|
||||||
|
4) 요약/비교 보기에서 최선 응답 선택 또는 합성 요약 표시(선택)
|
||||||
|
|
||||||
## 기술적 고려사항
|
## 구현 방안
|
||||||
|
|
||||||
### 세션 관리
|
### 1) pywinauto로 창 제어 (즉시)
|
||||||
- **세션 파일 위치**: strace/fs_usage로 CLI가 읽는 파일 추적
|
- 창 탐색: 제목/클래스/프로세스 정규식 매칭 → 핸들 캐시
|
||||||
- **인증 오류 감지**: "Session expired", "Please log in" 패턴 매칭
|
- 전송: `pyperclip.copy(prompt)` → 창 활성화 → `^v` → `Enter`
|
||||||
- **헬스 체크**: claude me, gemini whoami로 세션 상태 확인
|
- 포커스 복구: 창이 최소화/비활성화되어도 활성화 및 z-order 올리기
|
||||||
- **세션 파일 권한**:
|
- 실패 처리: 최대 N회 재시도(백오프), 실패 원인(UIA 접근 거부/포커스 경합) 라벨링
|
||||||
- ~/.config/claude: 600 (user read/write only)
|
|
||||||
- ~/.config/gemini: 600 (user read/write only)
|
|
||||||
- ~/.config/codex: 600 (user read/write only)
|
|
||||||
|
|
||||||
### 입출력 제어
|
### 2) 로그 기반 수집 (권장)
|
||||||
- **파이프 모드 사용**: PTY 생성 회피로 TUI 방지 (2025-09-29 검증)
|
- PowerShell: 세션 시작 시 `Start-Transcript -Path logs/<name>.log -Append`
|
||||||
- **비동기 처리**: asyncio.subprocess로 여러 CLI 동시 제어
|
- Linux/WSL: `cli ... | tee -a logs/<name>.log`
|
||||||
- **환경변수 강제**: CI=1 TERM=dumb NO_COLOR=1 CLICOLOR=0
|
- 앱: watchdog으로 해당 파일 tail → 새 라인 감지 시 ANSI 제거→청크 이벤트 발행
|
||||||
|
- 장점: 화면 스크래핑 불필요, 클립보드 경합 없음, 회귀 테스트(골든 로그) 용이
|
||||||
|
|
||||||
### 응답 파싱
|
### 3) 스키마/파서/품질
|
||||||
- **ANSI 코드 제거**: 색상 코드, 스피너, ASCII 아트 정규식 제거
|
- JSONL 이벤트 스키마
|
||||||
- **응답 종료 판단**:
|
- `request/chunk/end/error/meta` + `model/window/log_path/latency` 등 메타 포함
|
||||||
- 프롬프트 재출현 감지
|
- 파서 규칙
|
||||||
- 타임아웃 기반 종료
|
- ANSI 제거, 박스문자(╭─ 등) 제거, 코드블록 fence 복원, 과다 리렌더 중복 제거
|
||||||
- **CLI별 커스텀 파서**: 각 CLI 출력 형식에 맞춘 개별 파서
|
- 종료 규칙
|
||||||
|
- 침묵 타임아웃(예: 1.5~2.0s) + 프롬프트 패턴 + 길이 상한(무한루프 차단)
|
||||||
|
- 품질 도구
|
||||||
|
- 골든 로그 스냅샷 비교, 어댑터 계약 테스트(창 포커스/전송/수집 단위)
|
||||||
|
|
||||||
### 유지보수 리스크
|
### 의존성 설치(uv 예시)
|
||||||
- CLI 업데이트시 파싱 로직 깨짐
|
```
|
||||||
- 지속적인 출력 형식 변경 추적 필요
|
uv venv
|
||||||
- 각 CLI 버전별 호환성 테스트 필수
|
# PowerShell: .venv\Scripts\Activate.ps1, CMD: .venv\Scripts\activate.bat
|
||||||
|
source .venv/Scripts/activate
|
||||||
|
uv add pywinauto pyperclip watchdog PySimpleGUI
|
||||||
|
```
|
||||||
|
|
||||||
## 리스크
|
## 검증 체크리스트
|
||||||
- CLI 업데이트로 인한 호환성 깨짐
|
- 창 매핑이 정확한가(제목/탭 이름 고정 규칙 권장)?
|
||||||
- 세션 만료 처리
|
- 전송이 안정적인가(포커스 회복, 재시도, 단축키 충돌 없음)?
|
||||||
- 각 서비스의 rate limiting
|
- 로그 tail이 끊김없이 이어지는가(파일 록/권한/경로 문제 해결)?
|
||||||
- 자동화 관련 제약사항
|
- ANSI/박스문자 제거 후 결과가 사람이 읽기 좋은가?
|
||||||
|
- 종료/타임아웃 규칙이 과소/과대 종료 없이 동작하는가?
|
||||||
|
- 에러 분류(Auth/Rate/Clipboard/Focus/Timeout)가 유용한가?
|
||||||
|
|
||||||
## 최종 배포 전략
|
## 리스크 및 대응
|
||||||
|
- 포커스/클립보드 경합: 전송 전 포커스 확인, 사용자 알림, 재시도/지연
|
||||||
|
- 창 제목/탭 변경: config.yaml에 정규식/우선순위, 주기적 재탐색
|
||||||
|
- CLI 업데이트로 출력 변화: 파서 룰/골든 로그 갱신 템플릿
|
||||||
|
- 민감정보 노출: 클립보드/로그 마스킹 가이드, 비밀키 복사 금지
|
||||||
|
- 세션 만료/레이트리밋: 안내 메시지 및 재시도 대기(백오프)
|
||||||
|
|
||||||
### 개발 및 배포 워크플로우
|
## 실현 가능성 평가 (로컬)
|
||||||
1. **로컬 개발**: 코드 작성 및 테스트
|
- 현재 실현 가능성: 85~95% (사용자 수동 세션 유지 전제)
|
||||||
2. **Git Push**: 로컬 → Git 저장소
|
- PoC(1~2일): 창 탐색/전송/로그 tail/ANSI 제거/JSONL 이벤트
|
||||||
3. **서버 배포**: 51124에서 git pull 후 실행
|
- 품질화(3~5일): 파서·종료·오류 복구·핫키/매크로·골든 로그/계약 테스트
|
||||||
|
|
||||||
### 구현 위치
|
## 권장 운영 수칙
|
||||||
- **51124 서버**: 모든 CLI 설치되어 있음, FastAPI 서버 실행
|
- 창별 고정 제목 규칙(예: "Claude-51123", "Claude-51124", "Gemini-WSL", "OpenAI-WSL")
|
||||||
- **51123 서버**: nginx 프록시로 51124:8888 연결
|
- 모든 창에 로그 파일 설정 후 시작(Transcript/tee), 로그 경로는 `logs/<name>.log`
|
||||||
|
- 질문은 가능하면 한 줄 입력(멀티라인은 붙여넣기 전 미리 정리)
|
||||||
|
- 클립보드 민감정보 금지, 앱에서 마스킹 규칙 유지
|
||||||
|
|
||||||
### 51124 서버 설정
|
## 부록 A: PowerShell/WSL 로그 설정 예시
|
||||||
- UV 버전: 0.8.4 (이미 설치됨)
|
PowerShell(창에서 한번 실행)
|
||||||
- 전용 디렉토리: /home/admin/multi-ai-cli/
|
```
|
||||||
- UV 가상환경 패키지: FastAPI, asyncio, pty
|
Start-Transcript -Path "C:\\path\\to\\multi-ai-local\\logs\\Claude-51123.log" -Append
|
||||||
- CLI 제어: Python subprocess/PTY (tmux 대신)
|
```
|
||||||
- 리소스 격리: nice -n 19 적용
|
|
||||||
- 포트: 8888
|
|
||||||
|
|
||||||
### 51123 nginx 프록시
|
WSL/Linux(예: Gemini)
|
||||||
- location /multi-ai/ → proxy_pass http://51124:8888/
|
```
|
||||||
- WebSocket 지원 헤더 설정 필요
|
gemini -m gemini-2.5-pro | tee -a /mnt/c/path/to/multi-ai-local/logs/Gemini-WSL.log
|
||||||
- root_path 프리픽스 설정: FastAPI(root_path="/multi-ai")
|
```
|
||||||
|
|
||||||
## 실현 가능성 평가
|
OpenAI(원샷 스트림 예)
|
||||||
|
```
|
||||||
|
openai chat.completions.create -m gpt-4o-mini -g user "What is 2+2?" --stream \
|
||||||
|
| tee -a /mnt/c/path/to/multi-ai-local/logs/OpenAI-WSL.log
|
||||||
|
```
|
||||||
|
|
||||||
### 최종 평가
|
## 부록 B: pywinauto 기반 전송 개념
|
||||||
- **51124 서버 기준**: 80-85% (모든 CLI 설치됨, 세션 파일 접근 가능)
|
- 창 찾기: 제목/클래스/프로세스 정규식으로 핸들 검색→캐시
|
||||||
- **핵심 결론**: 51124에서 구축 + 51123 nginx 프록시로 웹 제공
|
- 붙여넣기: `pyperclip.copy(text)` → `window.set_focus()` → `window.type_keys("^v{ENTER}")`
|
||||||
|
- 실패/재시도: UIA 액세스 예외, 최소화 상태, 포커스 경합 시 백오프 후 재시도
|
||||||
|
|
||||||
### Phase별 평가
|
간단 예제
|
||||||
- **Phase 1** (tmux 스크립트): 즉시 가능
|
```
|
||||||
- **Phase 2** (Python 래퍼): PTY 제어, 응답 종료 판단 난제
|
from pywinauto.application import Application
|
||||||
- **Phase 3-4** (FastAPI+웹 UI): 구현 가능
|
import pyperclip, time
|
||||||
- **Phase 5** (세션 공유·응답 분석): 복잡도 높음
|
|
||||||
|
|
||||||
## 100% 실현성 달성 방안
|
# 정확한 제목 또는 정규식으로 연결 (Windows Terminal/PowerShell/WSL)
|
||||||
|
app = Application(backend="uia").connect(title_re=r".*PowerShell.*|.*Windows Terminal.*|.*Ubuntu.*")
|
||||||
|
win = app.window(title_re=r".*PowerShell.*|.*Windows Terminal.*|.*Ubuntu.*")
|
||||||
|
|
||||||
### 검증된 아키텍처 참조 (2024년 기준)
|
pyperclip.copy("What is 2+2?")
|
||||||
- **Microsoft Magentic-One**: Orchestrator + 4개 특화 에이전트 (WebSurfer, FileSurfer, Coder, Terminal)
|
win.set_focus()
|
||||||
- **AWS Multi-Agent Orchestrator**: Amazon Bedrock 기반 동적 에이전트 할당
|
time.sleep(0.1) # 포커스 안정화
|
||||||
- **AutoGen Framework**: Microsoft의 대화형 에이전트 간 메시지 전달 프로토콜
|
win.type_keys("^v{ENTER}")
|
||||||
|
```
|
||||||
|
|
||||||
### 핵심 개선사항
|
## 부록 C: watchdog 파일 감시 샘플
|
||||||
1. **프로파일 기반 에이전트 정의**: 각 CLI를 명확한 역할과 능력으로 정의
|
```
|
||||||
2. **메모리 시스템**: SQLite로 대화 컨텍스트 유지, 에이전트 간 정보 공유
|
from watchdog.observers import Observer
|
||||||
3. **동적 오케스트레이션**: 관리 에이전트가 태스크 분배 및 응답 집계
|
from watchdog.events import FileSystemEventHandler
|
||||||
4. **계약 기반 테스트**: 각 CLI 어댑터의 입출력 명세 정의 및 자동 검증
|
from pathlib import Path
|
||||||
|
import time
|
||||||
|
|
||||||
### 논문 기반 검증 필요 항목
|
class LogHandler(FileSystemEventHandler):
|
||||||
- Multi-agent collaboration mechanisms 연구
|
def __init__(self, log_paths):
|
||||||
- LLM tool use reliability 패턴
|
self.offsets = {Path(p): 0 for p in log_paths}
|
||||||
- Stream processing backpressure 처리
|
|
||||||
- Contract testing for microservices 적용
|
|
||||||
|
|
||||||
## 반자동화 범위 정의
|
def on_modified(self, event):
|
||||||
|
if event.is_directory:
|
||||||
|
return
|
||||||
|
p = Path(event.src_path)
|
||||||
|
if p not in self.offsets:
|
||||||
|
self.offsets[p] = 0
|
||||||
|
with p.open('r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
f.seek(self.offsets[p])
|
||||||
|
for line in f:
|
||||||
|
clean = line.rstrip('\n')
|
||||||
|
if clean:
|
||||||
|
print(f"chunk@{p.name}: ", clean)
|
||||||
|
self.offsets[p] = f.tell()
|
||||||
|
|
||||||
### 자동화 영역
|
logs = [
|
||||||
- **입력 브로드캐스트**: 1회 입력 → 다중 모델·23/24 서버에 전송, 응답 수집·요약·충돌 정리
|
r"C:\\path\\to\\multi-ai-local\\logs\\Claude-51123.log",
|
||||||
- **코드/문서 생성**: 파일별 unified diff, 커밋/PR 본문 템플릿, 체크리스트·재현 스크립트 자동 생성
|
r"C:\\path\\to\\multi-ai-local\\logs\\Claude-51124.log",
|
||||||
- **모니터링**: 헬스체크·로그 수집은 읽기 전용으로 자동화, 실행할 서버 명령은 "미리보기"로 제시
|
r"C:\\path\\to\\multi-ai-local\\logs\\Gemini-WSL.log",
|
||||||
- **세션 관리**: 쿠키 관리·만료 감지·재로그인 안내 자동, 만료 시만 수동 로그인 요구
|
]
|
||||||
|
|
||||||
### 승인 필요 지점
|
observer = Observer()
|
||||||
- 패치 적용
|
handler = LogHandler(logs)
|
||||||
- 테스트 실행
|
for lp in logs:
|
||||||
- git push/PR 생성
|
observer.schedule(handler, Path(lp).parent.as_posix(), recursive=False)
|
||||||
- 23/24 서버 배포/롤백 트리거
|
observer.start()
|
||||||
- 운영 DB/컨테이너 조작 (항상 수동)
|
try:
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
observer.stop()
|
||||||
|
observer.join()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 부록 D: PySimpleGUI 샘플 UI
|
||||||
|
```
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
layout = [
|
||||||
|
[sg.Multiline(size=(60, 15), key='-CLAUDE-'), sg.Multiline(size=(60, 15), key='-GEMINI-')],
|
||||||
|
[sg.Multiline(size=(60, 15), key='-OPENAI-')],
|
||||||
|
[sg.InputText(key='-PROMPT-', size=(100,1))],
|
||||||
|
[sg.Button('Broadcast'), sg.Button('Clear')]
|
||||||
|
]
|
||||||
|
window = sg.Window('Multi-AI Local Interface', layout)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
if event == sg.WIN_CLOSED:
|
||||||
|
break
|
||||||
|
if event == 'Broadcast':
|
||||||
|
prompt = values['-PROMPT-']
|
||||||
|
# TODO: pywinauto 어댑터로 선택된 창들에 붙여넣기+Enter 전송
|
||||||
|
# 로그 tail 스레드/프로세스가 window['-CLAUDE-'].update(..., append=True) 등으로 갱신
|
||||||
|
if event == 'Clear':
|
||||||
|
for k in ('-CLAUDE-','-GEMINI-','-OPENAI-'):
|
||||||
|
window[k].update('')
|
||||||
|
window.close()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 부록 E: 참고 문서 링크
|
||||||
|
- pywinauto: https://pywinauto.readthedocs.io/en/latest/
|
||||||
|
- PowerShell Start-Transcript: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.host/start-transcript
|
||||||
|
- Linux tee: https://www.geeksforgeeks.org/tee-command-linux-example/
|
||||||
|
- watchdog Quickstart: https://python-watchdog.readthedocs.io/en/stable/quickstart.html
|
||||||
|
- PySimpleGUI: https://www.pysimplegui.org/en/latest/
|
||||||
|
|
||||||
|
## 결론
|
||||||
|
본 문서는 웹/서버/프록시를 배제하고 로컬 Windows 환경에서 다중 터미널 창을 pywinauto로 제어하는 실용적 방안을 제시한다. 사용자가 세션을 수동 관리하는 전제에서, 입력 브로드캐스트와 로그 기반 응답 수집을 통해 안정적으로 Multi-AI 질의·응답을 통합할 수 있다.
|
||||||
|
|
||||||
|
|
||||||
|
# Windows Multi-AI Local Controller 구현 가이드
|
||||||
|
|
||||||
|
## 필요 라이브러리
|
||||||
|
pip/uv 중 편한 도구로 설치
|
||||||
|
```
|
||||||
|
pip install pywinauto pyperclip watchdog PySimpleGUI
|
||||||
|
# 또는
|
||||||
|
uv venv && source .venv/Scripts/activate
|
||||||
|
uv add pywinauto pyperclip watchdog PySimpleGUI
|
||||||
|
```
|
||||||
|
|
||||||
|
## 1. pywinauto - 창 제어
|
||||||
|
|
||||||
|
### 기본 설정
|
||||||
|
```
|
||||||
|
from pywinauto.application import Application
|
||||||
|
|
||||||
|
# UIA 백엔드 사용 (Windows Terminal 등 최신 앱)
|
||||||
|
app = Application(backend="uia")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 창 찾기(예: 제목 포함 여부로 탐색)
|
||||||
|
```
|
||||||
|
from pywinauto import Desktop
|
||||||
|
|
||||||
|
windows = Desktop(backend="uia").windows()
|
||||||
|
powershell_window = None
|
||||||
|
for w in windows:
|
||||||
|
if "PowerShell" in w.window_text():
|
||||||
|
powershell_window = w
|
||||||
|
break
|
||||||
|
```
|
||||||
|
|
||||||
|
### 입력 전송(붙여넣기 + Enter)
|
||||||
|
```
|
||||||
|
import pyperclip, time
|
||||||
|
|
||||||
|
pyperclip.copy(prompt)
|
||||||
|
powershell_window.set_focus()
|
||||||
|
time.sleep(0.05) # 포커스 안정화
|
||||||
|
powershell_window.type_keys("^v{ENTER}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. PowerShell Start-Transcript - 로그 생성
|
||||||
|
|
||||||
|
### 각 창에서 실행할 명령(예)
|
||||||
|
```
|
||||||
|
# PowerShell 창에서 실행
|
||||||
|
Start-Transcript -Path "C:\\path\\to\\multi-ai-local\\logs\\Claude-51123.log" -Append
|
||||||
|
|
||||||
|
# SSH 접속 후 Claude 실행
|
||||||
|
ssh admin@51123
|
||||||
|
claude
|
||||||
|
```
|
||||||
|
|
||||||
|
### WSL/Linux에서 tee 사용
|
||||||
|
```
|
||||||
|
# Gemini 실행하면서 로그 저장
|
||||||
|
gemini -m gemini-2.5-pro | tee -a /mnt/c/path/to/multi-ai-local/logs/Gemini-WSL.log
|
||||||
|
|
||||||
|
# OpenAI 스트림 실행
|
||||||
|
openai chat.completions.create -m gpt-4o-mini -g user "prompt" --stream \
|
||||||
|
| tee -a /mnt/c/path/to/multi-ai-local/logs/OpenAI-WSL.log
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. pyperclip - 클립보드 처리
|
||||||
|
|
||||||
|
### 안전한 사용법
|
||||||
|
```
|
||||||
|
import pyperclip, time
|
||||||
|
|
||||||
|
# 클립보드 초기화
|
||||||
|
pyperclip.copy("")
|
||||||
|
|
||||||
|
# 텍스트 복사
|
||||||
|
pyperclip.copy(prompt)
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
# 현재 클립보드 내용 확인
|
||||||
|
content = pyperclip.paste()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 에러 처리
|
||||||
|
```
|
||||||
|
from pyperclip import PyperclipException
|
||||||
|
|
||||||
|
try:
|
||||||
|
pyperclip.copy(text)
|
||||||
|
except PyperclipException as e:
|
||||||
|
print(f"클립보드 에러: {e}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. watchdog - 로그 파일 감시(간단형)
|
||||||
|
|
||||||
|
### 파일 변경 감지
|
||||||
|
```
|
||||||
|
from watchdog.observers import Observer
|
||||||
|
from watchdog.events import FileSystemEventHandler
|
||||||
|
|
||||||
|
class LogHandler(FileSystemEventHandler):
|
||||||
|
def __init__(self, log_path):
|
||||||
|
self.log_path = log_path
|
||||||
|
self.last_position = 0
|
||||||
|
|
||||||
|
def on_modified(self, event):
|
||||||
|
if event.src_path == self.log_path:
|
||||||
|
with open(self.log_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
f.seek(self.last_position)
|
||||||
|
new_content = f.read()
|
||||||
|
self.last_position = f.tell()
|
||||||
|
if new_content:
|
||||||
|
cleaned = remove_ansi(new_content)
|
||||||
|
process_output(cleaned) # TODO: UI 업데이트/이벤트 발행
|
||||||
|
|
||||||
|
observer = Observer()
|
||||||
|
handler = LogHandler("logs/Claude-51123.log")
|
||||||
|
observer.schedule(handler, path='logs/', recursive=False)
|
||||||
|
observer.start()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. ANSI 이스케이프 코드 제거
|
||||||
|
|
||||||
|
### 정규식 패턴
|
||||||
|
```
|
||||||
|
import re
|
||||||
|
|
||||||
|
def remove_ansi(text: str) -> str:
|
||||||
|
# 표준 ANSI 제거 패턴
|
||||||
|
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]')
|
||||||
|
return ansi_escape.sub('', text)
|
||||||
|
|
||||||
|
def remove_ansi_comprehensive(text: str) -> str:
|
||||||
|
pattern = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
||||||
|
return pattern.sub('', text)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 창 제목 규칙 (권장)
|
||||||
|
|
||||||
|
### PowerShell 창 제목 설정
|
||||||
|
```
|
||||||
|
$host.ui.RawUI.WindowTitle = "Claude-51123"
|
||||||
|
$host.ui.RawUI.WindowTitle = "Claude-51124"
|
||||||
|
$host.ui.RawUI.WindowTitle = "Gemini-WSL"
|
||||||
|
$host.ui.RawUI.WindowTitle = "OpenAI-WSL"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 전체 구조 예시(tkinter 대안)
|
||||||
|
```
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import scrolledtext
|
||||||
|
from pywinauto import Desktop
|
||||||
|
import pyperclip, time
|
||||||
|
|
||||||
|
class MultiAIController:
|
||||||
|
def __init__(self):
|
||||||
|
self.root = tk.Tk()
|
||||||
|
self.root.title("Multi-AI Local Controller")
|
||||||
|
self.windows = {}
|
||||||
|
|
||||||
|
def find_windows(self):
|
||||||
|
window_titles = {
|
||||||
|
"Claude-51123": "claude_23",
|
||||||
|
"Claude-51124": "claude_24",
|
||||||
|
"Gemini-WSL": "gemini",
|
||||||
|
"OpenAI-WSL": "openai",
|
||||||
|
}
|
||||||
|
desktop = Desktop(backend="uia")
|
||||||
|
for window in desktop.windows():
|
||||||
|
title = window.window_text()
|
||||||
|
for pattern, key in window_titles.items():
|
||||||
|
if pattern in title:
|
||||||
|
self.windows[key] = window
|
||||||
|
|
||||||
|
def send_to_window(self, window_key: str, prompt: str):
|
||||||
|
if window_key in self.windows:
|
||||||
|
window = self.windows[window_key]
|
||||||
|
pyperclip.copy(prompt)
|
||||||
|
time.sleep(0.01)
|
||||||
|
window.set_focus()
|
||||||
|
window.type_keys("^v{ENTER}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 주의사항
|
||||||
|
- 창 포커스: 활성화 실패 시 재시도/지연 필요
|
||||||
|
- 클립보드 충돌: 타이밍 조절, 민감정보 마스킹
|
||||||
|
- 로그 인코딩: UTF-8 사용, BOM 제거 권장
|
||||||
|
- 권한: 일부 창 제어에 관리자 권한 필요할 수 있음
|
||||||
|
- 타임아웃: CLI별 응답 시간·레이트리밋 고려
|
||||||
|
|
||||||
|
## 9. 테스트 체크리스트
|
||||||
|
- [ ] 창 탐색 정확성(제목 규칙/정규식)
|
||||||
|
- [ ] 클립보드 복사/붙여넣기 안정성
|
||||||
|
- [ ] 로그 파일 실시간 감지 및 tail 정확성
|
||||||
|
- [ ] ANSI 코드/박스문자 완전 제거
|
||||||
|
- [ ] 멀티 창 동시 제어(브로드캐스트/선택 전송)
|
||||||
|
- [ ] 에러 처리 및 복구(포커스/클립보드/타임아웃)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user