aeyes-local-agent
Officialby AEyesTech
README.md
# aeyes-local-agent
AeyeStudio AI 채팅이 **내 PC**의 파일·엑셀·클립보드·앱·셸·화면·마우스·키보드·로컬 DB를 쓸 수 있게 하는 로컬 MCP 에이전트입니다.
AeyeStudio 서버가 PC에 접속하지 않습니다 — 크롬의 AeyeStudio 탭이 `127.0.0.1`의 에이전트에 붙어 요청을 중계합니다.
두 가지 방법으로 실행합니다. 같은 설정 폴더를 쓰므로 **둘 중 하나만** 실행하세요(동시에 띄우면 두 번째가 거부됩니다).
- **데스크톱 앱(권장)**: [GitHub Releases](https://github.com/AEyesTech/aeyes-local-agent/releases)에서 macOS `.dmg` / Windows `.exe` 설치.
- **터미널**: `npx aeyes-local-agent`
## 데스크톱 앱
설치 후 실행하면 메뉴 막대(macOS)·알림 영역(Windows)에 아이콘이 생깁니다. 처음에는 페어링 코드 창이 뜹니다.
트레이 메뉴:
- **새 기기 연결…** — 6자리 코드(5분·1회용). AeyeStudio **설정 > 내 PC 연결**에 입력합니다. [코드 복사] 가능.
- **연결된 브라우저** — 연결별 해제, 모두 해제.
- **허용 폴더** — 폴더 열기·목록에서 제거(마지막 하나는 제거 불가)·폴더 추가(루트·홈 자체·홈 상위·설정 폴더를 포함하는 폴더는 불가).
- **항상 허용** — 기록된 범위 보기·하나씩 제거·모두 초기화.
- **이 세션 동안 허용 취소** — 마우스·키보드 세션 허용을 즉시 취소.
- **감사 로그 열기**, **로그인 시 자동 실행**, **업데이트 확인**, **종료**. macOS 는 **화면 기록·손쉬운 사용 권한 안내**.
PC 확인이 필요한 요청은 **항상 위에 뜨는 확인 창**으로 묻습니다. 창은 포커스를 빼앗지 않고, 버튼은 1초 뒤에 눌리며, 120초 동안 답이 없으면 거부합니다.
버튼: [허용] · [항상 허용 (범위: …)](범위를 좁힐 수 있을 때만) · [이 세션 동안 허용](마우스·키보드만) · [거부]. `Esc` 는 언제든 [거부]입니다. 창을 띄우지 못하면 거부로 처리합니다.
설치 경고: 서명 인증서 없이 만든 빌드는 macOS 에서 "확인되지 않은 개발자" 경고가 뜹니다(Finder 에서 앱을 Control-클릭 > 열기). Windows 는 SmartScreen 에서 [추가 정보] > [실행]. 이런 빌드는 macOS 자동 업데이트가 적용되지 않습니다.
## 터미널 실행
```bash
npx aeyes-local-agent
```
터미널에 표시되는 6자리 코드를 AeyeStudio **설정 > 내 PC 연결**에 입력하면 연결됩니다.
실행 중 명령: `p` 새 코드, `u` 모든 연결 해제, `g` 항상 허용 목록 보기, `r` 항상 허용·세션 허용 모두 지우기, `q` 종료.
PC 확인 프롬프트에는 매번 새로 뽑은 **2자리 코드**(10~99)가 보입니다. 코드가 `47` 이면:
| 입력 | 결과 |
|---|---|
| `47` | 이번만 허용 |
| `47a` | 항상 허용(프롬프트에 `[47a]` 가 보일 때만) |
| `47s` | 이 세션 동안 허용(마우스·키보드, 프롬프트에 `[47s]` 가 보일 때만) |
| 그 밖의 모든 입력(`y`, `a`, 빈 줄, 틀린 코드 포함) | 거부 |
프롬프트가 뜨기 전에 쳐 둔 입력과 뜬 직후 0.3초 안에 들어온 줄은 답으로 보지 않고 버립니다(자동 입력이 미리 쳐 둔 글이 확인을 통과하지 못하게). 그럴 때는 코드를 다시 입력하세요.
옵션: `--allow-dir <경로>`(허용 폴더 추가), `--port <47821-47830>`, `--config-dir <경로>`.
파일시스템 루트, 홈 폴더 자체나 그 상위, 설정 폴더를 포함하는 폴더는 `--allow-dir`로 줄 수 없습니다(종료 코드 1).
### 동작 주의사항
- `aeyes-local-agent unpair --all`, `db add`, `db remove` 는 에이전트(터미널·데스크톱 앱) 실행 중에는 거부됩니다(종료 코드 1).
- 같은 설정 폴더로 에이전트를 두 번 띄울 수 없습니다("이미 에이전트가 실행 중입니다(pid N)").
- 터미널 입력이 없는 환경에서는 PC 확인이 필요한 호출이 모두 즉시 거부됩니다. 확인이 10개 넘게 밀려 있으면 새 확인은 즉시 거부됩니다.
## 도구
| 도구 | 설명 | PC 확인 |
|---|---|---|
| fs_list / fs_stat / fs_read / fs_search | 허용 폴더 읽기 | 없음 |
| fs_write / excel_write | 파일·엑셀 쓰기 | 덮어쓸 때 |
| fs_mkdir / clipboard_write | 폴더 생성, 클립보드 쓰기 | 없음 |
| fs_move / fs_delete(휴지통) | 이동·삭제 | 항상 |
| excel_read | xlsx/csv 읽기 | 없음 |
| clipboard_read | 클립보드 읽기 | 항상 |
| open_path / open_app | URL·파일·앱 열기 | 항상 |
| shell_exec | 셸 명령(60초, 출력 64KB) | 항상 |
| screenshot | 화면 캡처(PNG, `display` 로 모니터 선택) | 항상 |
| mouse_move / mouse_click / keyboard_type / keyboard_press | 마우스·키보드 제어 | 항상("이 세션 동안 허용" 가능) |
| db_query | 설정한 로컬 DB 에 SQL 한 문장 | 쓰기 쿼리일 때(읽기 전용 DB 는 쓰기 거부) |
"항상 허용"을 고르면 표시된 범위는 다시 묻지 않습니다. 범위는 좁게 잡습니다.
- `shell_exec`: 명령 첫 단어 단위(예: `shell_exec:git`). 단 `` ; & | $ ` ( ) { } < > `` 문자나 줄바꿈이 들어간 명령, 글롭 문자(`*` `?` `[ ]`)가 들어간 명령, `=` 대입이 있는 명령,
경로로 시작하는 명령, `sudo`·`env`·`xargs`·`bash`·`powershell`·`node`·`python`·`open` 같은 래퍼·인터프리터 명령은 항상 허용할 수 없고 매번 묻습니다.
설정 폴더를 가리키는 명령(설정 폴더 경로·`~` 상대 경로·폴더 이름, `aeyes-agent`(대소문자 무시), `config.json` 이 들어간 명령 — 따옴표·`\` 로 쪼갠 이름 포함)도 이미 항상 허용된 명령이라도 매번 묻습니다.
이 검사는 명령 글자만 봅니다. 경로를 적지 않고 읽는 명령(아래 "보안" 참고)은 잡지 못합니다.
- `open_path`: 웹 주소(http/https)만 `open_path:url` 로 항상 허용할 수 있습니다. 파일·폴더 열기는 매번 묻고, 실행 파일·실행기 형식(`.app`, `.exe`, `.bat`, `.sh`, `.command`, `.lnk` 등)은 열지 않습니다.
- `open_app`: 인자 없이 실행할 때만 앱 이름 단위로 항상 허용할 수 있습니다.
- 마우스·키보드와 `db_query` 쓰기는 항상 허용할 수 없습니다.
## 화면·마우스·키보드 도구
- 선택 구성요소(`@nut-tree-fork/nut-js`, `screenshot-desktop`)를 쓰며, 설치·로드에 실패한 PC 에서는 이 도구들만 나타나지 않습니다(터미널에 안내가 나옵니다).
- **이 세션 동안 허용**: 마우스·키보드 확인 창에서 고르면 같은 브라우저 연결에서 60분 동안(또는 에이전트 종료·트레이 "이 세션 동안 허용 취소"·터미널 `r` 까지) 다시 묻지 않습니다. 설정 파일에는 저장되지 않습니다.
- PC 확인 창이 떠 있는 동안에는 마우스·키보드 도구가 `busy` 로 거부되고, 마우스·키보드 동작 중에 온 확인은 동작이 끝나고 1초가 더 지난 뒤에 뜹니다(자동 입력이 확인 창을 누르지 못하게).
- `keyboard_type` 은 한 번에 최대 500자입니다. 마우스·키보드 동작이 30초 안에 끝나지 않으면 `timeout` 으로 실패하고, 그 동작이 실제로 끝날 때까지(최대 60초) 새 PC 확인은 띄우지 않고 즉시 거부합니다(감사 로그에 `denied`).
- 좌표는 논리 픽셀입니다. `keyboard_press` 키: a-z, 0-9, f1-f12, enter, escape, tab, space, backspace, delete, 방향키, home, end, pageup, pagedown, shift, control, alt, meta(macOS Cmd·Windows Win).
### macOS 권한
시스템 설정 > 개인정보 보호 및 보안에서 켜야 합니다(데스크톱 앱은 "AeyeStudio Agent", 터미널 실행은 사용 중인 터미널 앱).
- **화면 기록** — `screenshot`. 없으면 창 없이 배경만 찍힐 수 있습니다.
- **손쉬운 사용** — 마우스·키보드 도구. 없으면 동작하지 않습니다.
권한을 켠 뒤에는 앱(또는 터미널)을 다시 시작하세요. 데스크톱 앱은 첫 실행 때 안내하고, 트레이 메뉴에서 다시 볼 수 있습니다.
## 로컬 DB 조회(db_query)
PostgreSQL·MySQL 을 지원합니다. 에이전트를 종료한 상태에서 등록합니다.
```bash
npx aeyes-local-agent db add shop --kind postgres # 읽기 전용(기본)
npx aeyes-local-agent db add erp --kind mysql --read-write
npx aeyes-local-agent db list
npx aeyes-local-agent db remove shop
```
- 연결 문자열(`postgres://사용자:비밀번호@호스트:5432/DB`, `mysql://사용자:비밀번호@호스트:3306/DB`)은 명령 인자로 받지 않고 실행 후 입력받습니다(화면에 보이지 않음). 설정 파일(권한 0600, 폴더 0700)에만 저장되고 도구 설명·결과·오류·목록·감사 로그에 나오지 않습니다.
- 모든 도구 결과(예: `shell_exec` 출력)에서 등록된 연결 문자열과 그 비밀번호는 `***` 로 가려집니다. 다만 연결 문자열은 아직 운영체제 키체인이 아니라 설정 파일에 있으므로, 설정 폴더를 읽는 명령은 확인 창에서 거부하세요.
- 데스크톱 앱 사용자도 이 명령으로 등록합니다(앱을 종료한 뒤 실행, 다시 앱 실행).
- **읽기 전용 DB**: `SELECT`·`WITH`·`VALUES`·`TABLE`·`SHOW`·`EXPLAIN`·`DESCRIBE` 같은 읽기만 실행합니다. 데이터 변경 CTE, `SELECT … INTO`, `EXPLAIN ANALYZE`, `nextval`·`pg_terminate_backend`·`load_file` 같은 부작용 함수, 여러 문장, MySQL 실행 주석(`/*! */`)은 거부합니다. 읽기는 DB 의 읽기 전용 트랜잭션 안에서 실행합니다.
- **읽기·쓰기 DB**: 쓰기 쿼리는 매번 PC 에서 확인합니다(항상 허용 없음).
- 결과는 최대 1000행(기본 200)·약 60KB, 타임아웃 기본 30초(최대 120초), 연결 10초.
- MySQL 은 타임아웃이 나면 에이전트가 기다리기를 멈추고 연결을 닫지만, 서버 쪽 쓰기(특히 COMMIT 도중)는 멈추지 않고 끝까지 반영될 수 있습니다. 쓰기 결과가 `timeout` 이면 DB 에서 실제 반영 여부를 확인하세요.
- 가장 안전한 방법은 **읽기 전용 DB 계정**을 만들어 등록하는 것입니다.
## 보안
- `127.0.0.1`에만 열리고, `Host`가 `127.0.0.1`/`localhost`가 아니면 거부합니다(DNS 리바인딩 방어).
- `https://studio.aeyes.dev`, `https://seller-ai-studio.vercel.app`에서 온 요청만 받습니다.
- 페어링 토큰은 해시로만 저장합니다. 코드는 5분·1회용, 5회 실패 시 10분 잠금.
- 파일 도구는 허용 폴더(기본 `~/Documents/AeyeStudio`, Windows 는 OneDrive 문서 폴더가 있으면 그 아래) 밖을 거부합니다. 심볼릭 링크는 실제 경로 기준으로 검사하고, 끊어진 링크는 거부합니다. 설정 폴더는 허용 폴더 안에 있어도 접근할 수 없습니다.
- 확인 프롬프트·확인 창은 원격에서 온 글의 제어 문자를 보이는 형태로 바꿔 표시합니다(요약 500자까지, 길면 앞뒤를 함께 표시).
- 설정·감사 로그 위치: macOS/Linux `~/.aeyes-agent/`, Windows `%APPDATA%\aeyes-agent\`.
- 모든 도구 호출은 설정 폴더의 `audit.log`에 남습니다(파일 내용·클립보드·입력한 글·DB 결과 제외, SQL 은 앞 120자).
- **알려진 한계 — `shell_exec` 항상 허용과 설정 폴더**: 설정 폴더(페어링 해시·DB 연결 문자열)를 가리키는 명령을 걸러 내는 검사는 명령 글자를 비교할 뿐입니다. `grep -r password ~` 나 `find ~ -name …` 처럼 경로를 직접 적지 않고 홈 폴더를 재귀로 읽는 명령, 환경에 따라 달라지는 경로는 글자 비교로 잡을 수 없습니다. 그러니 `grep`·`cat`·`find` 같은 범용 읽기 명령은 항상 허용하지 말고, 범위가 좁고 결과를 예측할 수 있는 명령만 항상 허용하세요. 확인 창에서는 명령 전체를 읽고, 홈 폴더나 설정 폴더를 건드릴 수 있으면 거부하세요.
- **알려진 한계 — `shell_exec` 항상 허용과 확인 창**: `shell_exec`를 특정 명령으로 "항상 허용" 해 두면, 그 명령으로 실행된 프로세스가 스스로 입력을 합성해(예: macOS `osascript`/System Events, Windows `SendInput`) 화면 위의 확인 창 버튼을 클릭할 수 있습니다. 확인 창 자체는 이런 종류의 공격까지 막지는 못합니다. 그러니 `shell_exec`는 항상 허용 범위를 최대한 좁은 명령으로만 잡으세요(예: 특정 조회 스크립트 하나) — 범용 셸이나 인터프리터를 항상 허용하지 마세요.
## 저장소 구조
```
aeyes-local-agent/
├── .github/workflows/ci.yml # CI(core·desktop 테스트, 실제 DB 통합 테스트)
├── .github/workflows/release.yml # v* 태그 → 데스크톱 앱 빌드·GitHub Release 초안·npm 배포
├── packages/core/ # aeyes-local-agent (npm 패키지)
│ ├── src/
│ │ ├── cli.ts, dbCli.ts, secretInput.ts # 진입점·db 명령
│ │ ├── server.ts, mcp.ts # HTTP 서버·요청별 MCP 서버
│ │ ├── terminal.ts # 터미널 확인 프롬프트
│ │ ├── config.ts, paths.ts, pidFile.ts, audit.ts
│ │ ├── native.ts # 화면·입력 드라이버(선택 의존성)
│ │ ├── db/ # SQL 판별·pg/mysql 드라이버
│ │ ├── policy/ # 확인 게이트·항상/세션 허용
│ │ ├── security/ # 페어링·Origin·속도 제한
│ │ └── tools/ # 21개 도구
│ └── test/
├── packages/desktop/ # Electron 트레이 앱(aeyes-agent-desktop)
│ ├── src/main.ts # Electron 셸(나머지 모듈은 단위 테스트)
│ ├── static/ # 확인 창 렌더러·preload
│ ├── builder/config.cjs # electron-builder 설정
│ └── test/
├── package.json
├── pnpm-workspace.yaml
└── README.md
```
## 릴리스(관리자)
1. `packages/core/package.json`, `packages/core/src/version.ts`, `packages/desktop/package.json` 버전을 같은 값으로 올리고 커밋.
2. `git tag v<버전> && git push origin v<버전>` → `release` 워크플로가 macOS(dmg+zip, universal)·Windows(NSIS x64)를 빌드해 GitHub Release 를 **초안(draft)** 으로 만들고, `NPM_TOKEN` 시크릿이 있으면 `npm-release` 환경 승인 뒤 core 를 npm 에 배포합니다(provenance 포함).
3. 관리자가 GitHub Releases 에서 초안의 파일·노트를 확인한 뒤 **직접 게시(Publish release)** 합니다. 게시하기 전에는 다운로드 링크와 데스크톱 앱 자동 업데이트에 나타나지 않습니다.
npm 배포 잡은 `npm-release` 환경 뒤에 있어 승인 전에는 배포되지 않습니다. **저장소 Settings > Environments 에서 `npm-release` 환경을 만들고 Required reviewers(관리자)를 지정해야 합니다** — 환경을 만들지 않으면 GitHub 가 보호 규칙 없이 자동으로 만들어 태그만 밀어도 바로 배포됩니다. `NPM_TOKEN` 은 이 환경의 시크릿(또는 저장소 시크릿)으로 두고, 토큰은 배포 단계에만 전달됩니다.
워크플로 권한은 기본 읽기 전용이고, Release 를 만드는 잡만 `contents: write` 를 받습니다. 체크아웃은 자격 증명을 남기지 않고(`persist-credentials: false`), `softprops/action-gh-release` 는 커밋 SHA 로 고정되어 있습니다.
4. 서명 시크릿(없으면 미서명 빌드 + 경고): `MAC_CSC_LINK`, `MAC_CSC_KEY_PASSWORD`(Developer ID 인증서 p12 base64·암호), `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID`(공증), `WIN_CSC_LINK`, `WIN_CSC_KEY_PASSWORD`(Windows 코드 서명).
5. 데스크톱 앱은 electron-updater 로 GitHub Releases 의 (게시된) 새 버전을 6시간마다 확인합니다.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues