diff --git a/ideas/250929_happybell80_multi_ai_cli_integration.md b/ideas/250929_happybell80_multi_ai_cli_integration.md index c816915..bfddb6b 100644 --- a/ideas/250929_happybell80_multi_ai_cli_integration.md +++ b/ideas/250929_happybell80_multi_ai_cli_integration.md @@ -1,437 +1,87 @@ --- date: 2025-09-29 author: happybell80 -tags: [ai, cli, integration, automation, windows, pywinauto, local] -status: idea (local-pivot) +tags: [ai, cli, integration, automation, wsl, tmux, broadcast] +status: completed --- -# Multi-AI CLI 통합 시스템 구상 (로컬/Windows 전환) +# Multi-AI CLI 통합 시스템 (WSL/tmux 완성) ## 개요 -- 목적: 웹/서버/프록시 없이, Windows 로컬 환경에서 다수의 터미널 창(SSH/WSL/로컬)을 그대로 활용해 Claude/Gemini/OpenAI(CLI·스크립트)에게 동시에 질문하고, 응답을 수집·비교·요약 -- 전제: 사용자가 각 터미널 창에서 세션(인증·쿠키·토큰)을 수동으로 유지하며, 로그인/재인증은 사람이 직접 처리 -- 접근: Windows GUI 자동화(pywinauto) + 로그 tail 기반 수집으로 안정 제어. 화면 스크래핑 의존 최소화(클립보드 충돌 방지) +- WSL tmux 기반 6개 AI 동시 브로드캐스트 시스템 +- Windows pywinauto 방식 포기, WSL tmux로 전환 +- 2025-09-29 구현 완료 -## 현황 (로컬 중심) -- 사용 중: PowerShell/Windows Terminal/WSL 창 5개 이상 동시 실행, 일부는 SSH로 51123/51124 서버 접속 후 Claude 실행 -- 인증: 전부 수동(사용자). 앱은 인증을 대행하지 않음 -- 과거 웹/프록시/FastAPI 계획은 폐기. 본 문서는 “로컬 GUI 자동화” 기반으로 전면 전환 - -## 목표 -- 단일 입력창에서 다중 터미널에 질문을 브로드캐스트 -- 각 창의 응답을 안정적으로 수집(로그 tail 권장)하여 패널별로 스트리밍 표시 -- 응답 비교/요약, 재전송/타임아웃/실패 처리, 대화 이력(JSONL/선택적 SQLite) 제공 - -## 로컬 디렉토리 구조(제안) +## 최종 구조 ``` -multi-ai-local/ -├── app.py # 로컬 GUI(단일 입력창, 응답패널, 브로드캐스트/선택 전송) -├── adapters/ -│ ├── base.py # WindowAdapter 인터페이스(창 찾기/포커스/전송) -│ ├── claude_win.py # Claude 창 어댑터(제목 규칙, 로그 경로) -│ ├── gemini_win.py # Gemini 창 어댑터 -│ └── openai_win.py # OpenAI CLI/스크립트 어댑터 -├── logs/ # 각 창의 Transcript/tee 로그 (앱이 tail) -├── config.yaml # 창 제목/모델/로그경로/프롬프트패턴/타임아웃 매핑 -└── pyproject.toml # pywinauto, pyperclip, watchdog, (옵션) PySimpleGUI/PySide6 +/home/happybell/projects/ivada/multi-ai-cli-wsl/ +├── run.py # 메인 진입점 +├── src/ +│ └── controller.py # SimpleController 클래스 +├── bin/ +│ ├── tmux_setup.sh # tmux 세션 생성 (2×3 그리드) +│ └── restart_tmux.sh # 세션 재시작 +├── config/ +│ └── config.yaml # AI 설정 +└── logs/ # pipe-pane 로그 저장 ``` -## 핵심 아이디어: 창은 사람처럼 “운전”, 출력은 로그로 “수집” -- 입력(전송) - - 질문을 클립보드에 복사 - - 대상 창 활성화 → Ctrl+V → Enter - - 포커스 실패/지연은 재시도(백오프) 및 사용자 알림 -- 출력(수집) - - 각 창에서 `Start-Transcript`(PowerShell) 또는 `tee -a`로 파일 로그 기록 - - 앱은 watchdog으로 로그를 tail하며 라인/청크 단위로 이벤트(JSONL) 생성 - - 화면 복사(Ctrl+A→Ctrl+C)는 보조 수단(클립보드 경합 가능성) -- 정규화/종료 판단 - - ANSI/박스문자 제거, 코드블록 fence 보정 - - 종료: 침묵 타임아웃 + 프롬프트 패턴(예: `>`, `$`, `~$`, `C:\>`) +## 구현 내용 -## 동작 시나리오 (최소 5창 + 입력창) -1. PowerShell: `ssh admin@51123` 접속 → Claude 실행 -2. PowerShell: `ssh admin@51124` 접속 → Claude 실행 -3. Windows Terminal/WSL: Gemini CLI 실행(세션 유지) -4. Windows Terminal/WSL: OpenAI CLI/스크립트 실행(스트리밍 옵션 권장) -5. 로컬 PowerShell/WSL: 보조 CLI 또는 로그 뷰어 -6. 앱 하단: 단일 입력창(브로드캐스트/선택 전송 지원) +### tmux 세션 (bin/tmux_setup.sh) +- 2×3 그리드 6개 pane 생성 +- 마우스 지원 활성화 (mouse on) +- 스크롤백 10000줄 +- pane 경계선 강조 (colour51 for active) +- pipe-pane으로 각 pane 로그 자동 저장 -흐름 -1) 입력창에 “질문” 작성 → 다중 대상 선택 → 전송 -2) 각 창은 해당 CLI에 입력 전달되고 실행 -3) 로그 tail로 응답을 수집→패널 스트리밍 표출, 완료/오류 이벤트 발생 -4) 요약/비교 보기에서 최선 응답 선택 또는 합성 요약 표시(선택) +### 컨트롤러 (src/controller.py) +- tmux send-keys로 브로드캐스트 +- 메시지와 'C-m' 분리 전송 (Enter 키 이슈 해결) +- 6개 AI 기본 활성화 +- 직접 입력 질문, /명령어 구분 -## 구현 방안 - -### 1) pywinauto로 창 제어 (즉시) -- 창 탐색: 제목/클래스/프로세스 정규식 매칭 → 핸들 캐시 -- 전송: `pyperclip.copy(prompt)` → 창 활성화 → `^v` → `Enter` -- 포커스 복구: 창이 최소화/비활성화되어도 활성화 및 z-order 올리기 -- 실패 처리: 최대 N회 재시도(백오프), 실패 원인(UIA 접근 거부/포커스 경합) 라벨링 - -### 2) 로그 기반 수집 (권장) -- PowerShell: 세션 시작 시 `Start-Transcript -Path logs/.log -Append` -- Linux/WSL: `cli ... | tee -a logs/.log` -- 앱: watchdog으로 해당 파일 tail → 새 라인 감지 시 ANSI 제거→청크 이벤트 발행 -- 장점: 화면 스크래핑 불필요, 클립보드 경합 없음, 회귀 테스트(골든 로그) 용이 - -### 3) 스키마/파서/품질 -- JSONL 이벤트 스키마 - - `request/chunk/end/error/meta` + `model/window/log_path/latency` 등 메타 포함 -- 파서 규칙 - - ANSI 제거, 박스문자(╭─ 등) 제거, 코드블록 fence 복원, 과다 리렌더 중복 제거 -- 종료 규칙 - - 침묵 타임아웃(예: 1.5~2.0s) + 프롬프트 패턴 + 길이 상한(무한루프 차단) -- 품질 도구 - - 골든 로그 스냅샷 비교, 어댑터 계약 테스트(창 포커스/전송/수집 단위) - -### 의존성 설치(uv 예시) -``` -uv venv -# PowerShell: .venv\Scripts\Activate.ps1, CMD: .venv\Scripts\activate.bat -source .venv/Scripts/activate -uv add pywinauto pyperclip watchdog PySimpleGUI +### 설정 (config/config.yaml) +```yaml +session: ai-panel +panes: + claude_51123: "ai-panel:0.0" + gemini_local: "ai-panel:0.1" + claude_51124: "ai-panel:0.2" + codex_local: "ai-panel:0.3" + claude_local: "ai-panel:0.4" + openai_local: "ai-panel:0.5" +commands: # 모두 비움 (대화형 모드) + claude_51123: "" + gemini_local: "" ``` -## 검증 체크리스트 -- 창 매핑이 정확한가(제목/탭 이름 고정 규칙 권장)? -- 전송이 안정적인가(포커스 회복, 재시도, 단축키 충돌 없음)? -- 로그 tail이 끊김없이 이어지는가(파일 록/권한/경로 문제 해결)? -- ANSI/박스문자 제거 후 결과가 사람이 읽기 좋은가? -- 종료/타임아웃 규칙이 과소/과대 종료 없이 동작하는가? -- 에러 분류(Auth/Rate/Clipboard/Focus/Timeout)가 유용한가? +## 실행 방법 +```bash +# tmux 세션 시작 +restart_tmux.sh -## 리스크 및 대응 -- 포커스/클립보드 경합: 전송 전 포커스 확인, 사용자 알림, 재시도/지연 -- 창 제목/탭 변경: config.yaml에 정규식/우선순위, 주기적 재탐색 -- CLI 업데이트로 출력 변화: 파서 룰/골든 로그 갱신 템플릿 -- 민감정보 노출: 클립보드/로그 마스킹 가이드, 비밀키 복사 금지 -- 세션 만료/레이트리밋: 안내 메시지 및 재시도 대기(백오프) +# 각 pane에서 AI 수동 실행 +# Pane 0: ssh -p 51123 admin@ro-being.com → claude +# Pane 1: gemini +# Pane 2: ssh -p 51124 admin@ro-being.com → claude +# Pane 3: codex +# Pane 4: claude +# Pane 5: 예비 -## 실현 가능성 평가 (로컬) -- 현재 실현 가능성: 85~95% (사용자 수동 세션 유지 전제) -- PoC(1~2일): 창 탐색/전송/로그 tail/ANSI 제거/JSONL 이벤트 -- 품질화(3~5일): 파서·종료·오류 복구·핫키/매크로·골든 로그/계약 테스트 - -## 권장 운영 수칙 -- 창별 고정 제목 규칙(예: "Claude-51123", "Claude-51124", "Gemini-WSL", "OpenAI-WSL") -- 모든 창에 로그 파일 설정 후 시작(Transcript/tee), 로그 경로는 `logs/.log` -- 질문은 가능하면 한 줄 입력(멀티라인은 붙여넣기 전 미리 정리) -- 클립보드 민감정보 금지, 앱에서 마스킹 규칙 유지 - -## 부록 A: PowerShell/WSL 로그 설정 예시 -PowerShell(창에서 한번 실행) -``` -Start-Transcript -Path "C:\\path\\to\\multi-ai-local\\logs\\Claude-51123.log" -Append +# 별도 터미널에서 컨트롤러 +cd ~/projects/ivada/multi-ai-cli-wsl +uv run python run.py ``` -WSL/Linux(예: Gemini) -``` -gemini -m gemini-2.5-pro | tee -a /mnt/c/path/to/multi-ai-local/logs/Gemini-WSL.log -``` +## 해결한 이슈 +1. tmux send-keys 'Enter' 안됨 → 'C-m' 분리 전송 +2. 창 분할 오류 → tiled 레이아웃 사용 +3. Windows line ending → sed 제거 +4. 명령 템플릿 오류 → 비움 (raw text) +5. 스크롤/복사 충돌 → Shift+드래그 -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 기반 전송 개념 -- 창 찾기: 제목/클래스/프로세스 정규식으로 핸들 검색→캐시 -- 붙여넣기: `pyperclip.copy(text)` → `window.set_focus()` → `window.type_keys("^v{ENTER}")` -- 실패/재시도: UIA 액세스 예외, 최소화 상태, 포커스 경합 시 백오프 후 재시도 - -간단 예제 -``` -from pywinauto.application import Application -import pyperclip, time - -# 정확한 제목 또는 정규식으로 연결 (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.*") - -pyperclip.copy("What is 2+2?") -win.set_focus() -time.sleep(0.1) # 포커스 안정화 -win.type_keys("^v{ENTER}") -``` - -## 부록 C: watchdog 파일 감시 샘플 -``` -from watchdog.observers import Observer -from watchdog.events import FileSystemEventHandler -from pathlib import Path -import time - -class LogHandler(FileSystemEventHandler): - def __init__(self, log_paths): - self.offsets = {Path(p): 0 for p in log_paths} - - 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 = [ - r"C:\\path\\to\\multi-ai-local\\logs\\Claude-51123.log", - 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: - observer.schedule(handler, Path(lp).parent.as_posix(), recursive=False) -observer.start() -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 코드/박스문자 완전 제거 -- [ ] 멀티 창 동시 제어(브로드캐스트/선택 전송) -- [ ] 에러 처리 및 복구(포커스/클립보드/타임아웃) +## 제한사항 +- AI 로그인은 수동 +- 응답 파싱/비교 미구현 +- Gemini C-u 처리 미구현 \ No newline at end of file