데스크톱 앱
Tauri 기반 데스크톱 래퍼: 자동 페어링, 트레이 아이콘 상태, IPC 명령어, 권한 설정.
Revka 데스크톱 앱은 Tauri v2로 제작된 네이티브 크로스 플랫폼 래퍼(macOS, Linux, Windows)입니다. 브라우저에서 열 수 있는 것과 동일한 웹 대시보드를 네이티브 WebView로 구동하면서, 브라우저가 제공할 수 없는 세 가지 기능을 추가합니다. 로컬에서 실행 중인 게이트웨이와 자동 페어링되어 토큰을 직접 붙여넣을 필요가 없고, 에이전트 상태를 실시간으로 반영하는 아이콘과 함께 시스템 트레이에 상주하며, 백그라운드 헬스 폴러를 유지하여 트레이 아이콘이 항상 게이트웨이의 가동 여부를 표시합니다.
대시보드를 브라우저 탭 대신 메뉴바/트레이에 항상 열어 두고 싶을 때 데스크톱 앱을 사용하세요. 데스크톱 앱은 씬 클라이언트로, 에이전트 작업을 직접 처리하지 않으며 127.0.0.1에서 HTTP 및 WebSocket으로 게이트웨이와 통신합니다. 게이트웨이를 먼저 실행해야 합니다(대시보드 실행 및 revka gateway, daemon & service 참고).
앱 실행 또는 설치
섹션 제목: “앱 실행 또는 설치”CLI의 revka desktop 명령어로 설치된 데스크톱 앱을 찾아 실행하거나 다운로드 페이지를 열 수 있습니다.
revka desktop # 설치된 데스크톱 바이너리를 찾아 실행revka desktop --install # 다운로드 페이지 열기 (https://www.kumiho.io/download)| 플래그 | 설명 |
|---|---|
| (없음) | 설치된 데스크톱 바이너리를 찾아 실행합니다. 찾지 못하면 코드 1로 종료합니다. |
--install | 다운로드 페이지를 엽니다. macOS/Linux에서는 브라우저로 https://www.kumiho.io/download를 엽니다. |
--install 없이 실행하면 revka desktop은 다음 경로를 순서대로 탐색합니다: /Applications/Revka.app(macOS), CLI 바이너리와 동일한 디렉터리, ~/.cargo/bin/, ~/.local/bin/, 그 다음 PATH. 실행된 앱은 http://127.0.0.1:42617/_app/의 로컬 게이트웨이에 연결합니다.
전체 생명주기 명령어 참조는 revka install, update, migrate…를 참고하세요.
소스에서 빌드
섹션 제목: “소스에서 빌드”Tauri 앱은 apps/tauri/에 위치합니다. Tauri CLI로 빌드하세요.
cargo tauri dev개발 모드에서 WebView는 http://127.0.0.1:5173(Vite 개발 서버)을 로드합니다.
cargo tauri build릴리스 빌드는 http://127.0.0.1:42617/_app/의 게이트웨이에서 제공되는 대시보드를 번들링하고, 모든 번들 타겟용 설치 파일을 생성합니다.
apps/tauri/tauri.conf.json의 주요 설정:
| 설정 | 값 |
|---|---|
productName | Revka |
identifier | ai.kumihoio.desktop |
version | 2026.5.20 (CalVer, 게이트웨이 릴리스와 연동) |
| 윈도우 | 1200×800, 크기 조절 가능, 시작 시 숨김 (자동 페어링 후 표시) |
| 번들 타겟 | all — 아이콘: 32x32.png, 128x128.png, icon.icns, icon.ico |
콘텐츠 보안 정책
섹션 제목: “콘텐츠 보안 정책”앱의 CSP는 모든 네트워크 접근을 localhost로 제한합니다. 이는 데스크톱 앱의 주요 네트워크 보호 장치로, WebView가 원격 출처에 접근할 수 없습니다.
"csp": "default-src 'self' http://127.0.0.1:* ws://127.0.0.1:*; connect-src 'self' http://127.0.0.1:* ws://127.0.0.1:*; script-src 'self' 'unsafe-inline' http://127.0.0.1:*; style-src 'self' 'unsafe-inline' http://127.0.0.1:*; img-src 'self' http://127.0.0.1:* data:"WebSocket 연결도 마찬가지로 ws://127.0.0.1:*로 제한되며, 게이트웨이의 /ws/chat, /ws/canvas/{id}, /ws/nodes 엔드포인트를 포함합니다.
게이트웨이 자동 페어링
섹션 제목: “게이트웨이 자동 페어링”게이트웨이에서 페어링이 활성화된 경우(require_pairing = true), 브라우저 클라이언트는 일반적으로 일회용 페어링 코드 대화상자를 표시합니다. 데스크톱 앱은 이 과정을 완전히 건너뜁니다. 시작 시 localhost를 통해 자동으로 페어링하고 결과 토큰을 WebView에 주입하므로, React 프론트엔드가 이미 인증된 상태로 마운트됩니다.
이 흐름은 앱 시작 시 백그라운드 작업으로 실행됩니다.
-
페어링 필요 여부 확인. 앱이
GET /health를 호출하고 응답 본문의require_pairing불리언 값을 읽습니다. 페어링이 비활성화된 경우 토큰이 필요 없으며 흐름이 종료됩니다. -
유효한 토큰이 있으면 재사용. 앱에 이미 토큰이 있는 경우,
GET /api/status(Bearer 인증)로 유효성을 검사합니다. 유효한 토큰은 그대로 재사용됩니다. -
새 페어링 코드 요청. 유효한 토큰이 없으면 앱이 관리자 전용 엔드포인트
POST /admin/paircode/new에 POST 요청을 보내{"pairing_code": "<code>"}를 받습니다. 이 엔드포인트는 localhost에서만 접근 가능하므로 조용한 자동 페어링이 안전합니다. -
코드를 토큰으로 교환. 앱이
POST /pair에 헤더X-Pairing-Code: <code>를 포함하여 POST 요청을 보내고{"token": "<bearer>"}를 받습니다. -
WebView에 토큰 주입. 토큰이 WebView의
localStorage에revka_token키로 저장되어, React 앱이 이를 인식하고 수동 페어링 대화상자를 건너뜁니다.
관련 요청:
POST /admin/paircode/new HTTP/1.1Host: 127.0.0.1:42617{ "pairing_code": "123456" }POST /pair HTTP/1.1Host: 127.0.0.1:42617X-Pairing-Code: 123456{ "token": "<bearer-token>" }페어링 모델의 기반이 되는 코드, 토큰, 기기 등록에 대한 자세한 내용은 페어링 & 인증 및 시크릿, 페어링 & 기기 인증을 참고하세요.
백그라운드 헬스 폴러
섹션 제목: “백그라운드 헬스 폴러”백그라운드 비동기 작업이 5초마다(하드코딩된 POLL_INTERVAL = 5s) 게이트웨이를 폴링하여 앱 상태를 최신으로 유지합니다.
매 틱마다 폴러는:
GET /health를 호출합니다 — 성공하면 연결됨, 실패하면 연결 해제로 처리합니다.- 공유 상태(
connected: bool)를 업데이트합니다. - 현재 상태에 맞게 시스템 트레이 아이콘과 툴팁을 업데이트합니다.
bool페이로드와 함께 Tauri 이벤트revka://status-changed를 발생시킵니다.
WebView에서 실행되는 프론트엔드 코드는 revka://status-changed를 수신하여 게이트웨이를 직접 폴링하지 않고도 자체 연결 표시기를 업데이트할 수 있습니다.
import { listen } from "@tauri-apps/api/event";
await listen("revka://status-changed", (event) => { const connected = event.payload; // boolean // update UI…});시스템 트레이 아이콘과 메뉴
섹션 제목: “시스템 트레이 아이콘과 메뉴”앱은 연결 및 에이전트 상태를 실시간으로 반영하는 영구 트레이 아이콘(id main)을 설치합니다. 헬스 폴러가 set_icon() / set_tooltip()을 통해 임베드된 22×22 RGBA PNG 4종 중 하나를 선택합니다.
아이콘 상태
섹션 제목: “아이콘 상태”| 상태 | 아이콘 에셋 | 툴팁 | 조건 |
|---|---|---|---|
| 연결 해제 | icons/tray-disconnected.png | Revka — Disconnected | 게이트웨이 접근 불가 (/health 실패) |
| 유휴 | icons/tray-idle.png | Revka — Idle | 연결됨, 에이전트 미실행 |
| 작동 중 | icons/tray-working.png | Revka — Working | 연결됨, 에이전트 실행 중 |
| 오류 | icons/tray-error.png | Revka — Error | 연결됨, 에이전트 오류 보고 |
연결 해제 상태가 우선합니다. 게이트웨이에 접근할 수 없으면 마지막으로 알려진 에이전트 상태와 관계없이 툴팁은 항상 Revka — Disconnected입니다.
트레이 메뉴
섹션 제목: “트레이 메뉴”트레이 아이콘을 우클릭하면 메뉴가 열립니다. 아이콘을 직접 좌클릭하면 메인 창이 표시되고 포커스됩니다.
| 항목 | Id | 동작 |
|---|---|---|
| Show Dashboard | show | 메인 창을 표시하고 포커스 |
| Agent Chat | chat | 창을 표시하고 /agent 경로로 딥 링크 (window.location.hash = '/agent') |
| Status: Checking… | status | 정보 표시 전용 — 비활성화 상태, 클릭 불가 |
| Quit Revka | quit | 프로세스 종료 (app.exit(0)) |
Tauri 명령어 (IPC)
섹션 제목: “Tauri 명령어 (IPC)”앱은 React 프론트엔드와 네이티브 Rust 사이의 IPC 브리지로 Tauri 명령어 6개를 제공합니다. 프론트엔드는 invoke()로 이를 호출하며 인증 헤더를 직접 관리할 필요가 없습니다 — Bearer 토큰이 네이티브 공유 상태에 저장되어 모든 게이트웨이 요청에 자동으로 첨부됩니다. 각 명령어는 async이며 Result<serde_json::Value, String>(또는 get_health의 경우 Result<bool, String>)을 반환합니다.
| 명령어 | 호출 대상 | 인증 | 비고 |
|---|---|---|---|
get_status | GET /api/status | Bearer | 게이트웨이 상태 스냅샷 |
get_health | GET /health | 없음 | bool 반환 |
list_channels | GET /api/status | Bearer | 채널 정보는 상태 페이로드의 일부 |
initiate_pairing | POST /api/pairing/initiate | Bearer | UI에서 페어링 흐름 시작 |
get_devices | GET /api/devices | Bearer | 페어링된 기기 목록 |
send_message | POST /webhook with {"message": "<msg>"} | Bearer | 웹훅 인그레스를 통해 에이전트 파이프라인 실행 |
WebView에서의 호출 예시:
import { invoke } from "@tauri-apps/api/core";
const status = await invoke("get_status");await invoke("send_message", { message: "Summarize today's logs" });이 IPC 명령어들은 데스크톱 앱 위에 구축하기 위해 지원되는 인터페이스입니다. 래핑하는 엔드포인트는 게이트웨이 API 아래에 문서화되어 있습니다 — 상태, 헬스, 설정 & 도구 엔드포인트 및 페어링 & 인증을 참고하세요.
권한 설정
섹션 제목: “권한 설정”Tauri 권한은 WebView가 수행할 수 있는 작업을 선언합니다. 앱은 apps/tauri/capabilities/에 두 개의 권한 파일을 포함하며, 모두 main 창에 스코프됩니다.
{ "identifier": "default", "windows": ["main"], "permissions": [ "core:default", "shell:allow-open", "store:allow-get", "store:allow-set", "store:allow-save", "store:allow-load" ]}기본 권한: 핵심 작업, 시스템 브라우저에서 URL 열기(shell:allow-open), 영구 저장소 읽기/쓰기(store:allow-get/set/save/load).
{ "identifier": "desktop", "windows": ["main"], "permissions": [ "core:default", "shell:allow-open", "shell:allow-execute", "store:allow-get", "store:allow-set", "store:allow-save", "store:allow-load" ]}기본 권한 세트에 shell:allow-execute를 추가하여, 데스크톱 빌드가 고급 기능을 위해 시스템 명령어를 실행할 수 있도록 합니다.
권한과 CSP는 데스크톱 보안 모델의 두 가지 보완적인 계층을 형성합니다. CSP는 WebView가 접근할 수 있는 네트워크 출처를 제한하고(localhost 전용), 권한은 호출할 수 있는 네이티브 작업을 제한합니다. 전체 보안 모델에 대한 자세한 내용은 보안 모델을 참고하세요.
단일 인스턴스 강제
섹션 제목: “단일 인스턴스 강제”앱은 tauri_plugin_single_instance를 사용하여 항상 하나의 인스턴스만 실행됩니다. 트레이에서 앱이 이미 실행 중일 때 앱을 실행하거나 revka desktop을 실행해도 중복 인스턴스가 시작되지 않습니다 — 대신 기존 인스턴스의 메인 창이 표시되고 포커스됩니다. 이는 자동으로 작동하며 별도의 설정이 필요하지 않습니다. revka desktop을 다시 실행하면 이미 실행 중인 창이 전면에 표시되는 이유입니다.