콘텐츠로 이동

데스크톱 앱

Tauri 기반 데스크톱 래퍼: 자동 페어링, 트레이 아이콘 상태, IPC 명령어, 권한 설정.

Revka 데스크톱 앱은 Tauri v2로 제작된 네이티브 크로스 플랫폼 래퍼(macOS, Linux, Windows)입니다. 브라우저에서 열 수 있는 것과 동일한 웹 대시보드를 네이티브 WebView로 구동하면서, 브라우저가 제공할 수 없는 세 가지 기능을 추가합니다. 로컬에서 실행 중인 게이트웨이와 자동 페어링되어 토큰을 직접 붙여넣을 필요가 없고, 에이전트 상태를 실시간으로 반영하는 아이콘과 함께 시스템 트레이에 상주하며, 백그라운드 헬스 폴러를 유지하여 트레이 아이콘이 항상 게이트웨이의 가동 여부를 표시합니다.

대시보드를 브라우저 탭 대신 메뉴바/트레이에 항상 열어 두고 싶을 때 데스크톱 앱을 사용하세요. 데스크톱 앱은 씬 클라이언트로, 에이전트 작업을 직접 처리하지 않으며 127.0.0.1에서 HTTP 및 WebSocket으로 게이트웨이와 통신합니다. 게이트웨이를 먼저 실행해야 합니다(대시보드 실행revka gateway, daemon & service 참고).

CLI의 revka desktop 명령어로 설치된 데스크톱 앱을 찾아 실행하거나 다운로드 페이지를 열 수 있습니다.

Terminal window
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로 빌드하세요.

Terminal window
cargo tauri dev

개발 모드에서 WebView는 http://127.0.0.1:5173(Vite 개발 서버)을 로드합니다.

apps/tauri/tauri.conf.json의 주요 설정:

설정
productNameRevka
identifierai.kumihoio.desktop
version2026.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 프론트엔드가 이미 인증된 상태로 마운트됩니다.

이 흐름은 앱 시작 시 백그라운드 작업으로 실행됩니다.

  1. 페어링 필요 여부 확인. 앱이 GET /health를 호출하고 응답 본문의 require_pairing 불리언 값을 읽습니다. 페어링이 비활성화된 경우 토큰이 필요 없으며 흐름이 종료됩니다.

  2. 유효한 토큰이 있으면 재사용. 앱에 이미 토큰이 있는 경우, GET /api/status(Bearer 인증)로 유효성을 검사합니다. 유효한 토큰은 그대로 재사용됩니다.

  3. 새 페어링 코드 요청. 유효한 토큰이 없으면 앱이 관리자 전용 엔드포인트 POST /admin/paircode/new에 POST 요청을 보내 {"pairing_code": "<code>"}를 받습니다. 이 엔드포인트는 localhost에서만 접근 가능하므로 조용한 자동 페어링이 안전합니다.

  4. 코드를 토큰으로 교환. 앱이 POST /pair에 헤더 X-Pairing-Code: <code>를 포함하여 POST 요청을 보내고 {"token": "<bearer>"}를 받습니다.

  5. WebView에 토큰 주입. 토큰이 WebView의 localStoragerevka_token 키로 저장되어, React 앱이 이를 인식하고 수동 페어링 대화상자를 건너뜁니다.

관련 요청:

POST /admin/paircode/new HTTP/1.1
Host: 127.0.0.1:42617
{ "pairing_code": "123456" }
POST /pair HTTP/1.1
Host: 127.0.0.1:42617
X-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.pngRevka — Disconnected게이트웨이 접근 불가 (/health 실패)
유휴icons/tray-idle.pngRevka — Idle연결됨, 에이전트 미실행
작동 중icons/tray-working.pngRevka — Working연결됨, 에이전트 실행 중
오류icons/tray-error.pngRevka — Error연결됨, 에이전트 오류 보고

연결 해제 상태가 우선합니다. 게이트웨이에 접근할 수 없으면 마지막으로 알려진 에이전트 상태와 관계없이 툴팁은 항상 Revka — Disconnected입니다.

트레이 아이콘을 우클릭하면 메뉴가 열립니다. 아이콘을 직접 좌클릭하면 메인 창이 표시되고 포커스됩니다.

항목Id동작
Show Dashboardshow메인 창을 표시하고 포커스
Agent Chatchat창을 표시하고 /agent 경로로 딥 링크 (window.location.hash = '/agent')
Status: Checking…status정보 표시 전용 — 비활성화 상태, 클릭 불가
Quit Revkaquit프로세스 종료 (app.exit(0))

앱은 React 프론트엔드와 네이티브 Rust 사이의 IPC 브리지로 Tauri 명령어 6개를 제공합니다. 프론트엔드는 invoke()로 이를 호출하며 인증 헤더를 직접 관리할 필요가 없습니다 — Bearer 토큰이 네이티브 공유 상태에 저장되어 모든 게이트웨이 요청에 자동으로 첨부됩니다. 각 명령어는 async이며 Result<serde_json::Value, String>(또는 get_health의 경우 Result<bool, String>)을 반환합니다.

명령어호출 대상인증비고
get_statusGET /api/statusBearer게이트웨이 상태 스냅샷
get_healthGET /health없음bool 반환
list_channelsGET /api/statusBearer채널 정보는 상태 페이로드의 일부
initiate_pairingPOST /api/pairing/initiateBearerUI에서 페어링 흐름 시작
get_devicesGET /api/devicesBearer페어링된 기기 목록
send_messagePOST /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).

권한과 CSP는 데스크톱 보안 모델의 두 가지 보완적인 계층을 형성합니다. CSP는 WebView가 접근할 수 있는 네트워크 출처를 제한하고(localhost 전용), 권한은 호출할 수 있는 네이티브 작업을 제한합니다. 전체 보안 모델에 대한 자세한 내용은 보안 모델을 참고하세요.

앱은 tauri_plugin_single_instance를 사용하여 항상 하나의 인스턴스만 실행됩니다. 트레이에서 앱이 이미 실행 중일 때 앱을 실행하거나 revka desktop을 실행해도 중복 인스턴스가 시작되지 않습니다 — 대신 기존 인스턴스의 메인 창이 표시되고 포커스됩니다. 이는 자동으로 작동하며 별도의 설정이 필요하지 않습니다. revka desktop을 다시 실행하면 이미 실행 중인 창이 전면에 표시되는 이유입니다.