⚡ 3줄 요약
증상 — 스트림독(MiraBox Stream Dock)에 들어 있는 AI 비서 Komi에게 타이핑으로 한국어를 치면 잘 알아듣는다. 그런데 마이크에 대고 말하면 중국어(일본어 같기도 함)로 받아적는다.
원인 — 음성 인식을 내 PC가 아니라 MiraBox 서버가 한다. 앱은 언어값으로 zh 아니면 en만 보내는데, 서버는 그 값마저 무시하고 알아서 언어를 감지한다. 그러다 보니 짧은 한국어 발화를 중국어(일본어 같기도 함)로 잘못 읽는다.
해결 — app.asar를 세 군데 고쳐서 음성 인식 요청만 내 PC의 faster-whisper large-v3 컨테이너로 보냈다. 채팅 LLM은 건드리지 않고 STT만 갈아끼웠다. 앱에서 세 줄, 서버에서 열 줄이면 끝난다.
“Hi Komi” 하고 부르면 깨어나서 대화도 하고 볼륨도 조절해주는 데스크톱 캐릭터 비서. 스트림독 쓰는 사람이면 한 번쯤 봤을 그 Komi 얘기다. 그런데 얘가 내 한국어를 못 알아들어서 고쳐 보았다. 이제 내 말을 알아들으니 무척 맘에 든다.
내가 갖고 있는 스트림독은 알리에서 구매한 Mirabox N3이고, Stream Dock 소프트웨어를 설치해서 Komi AI를 설치한 상태다.


왜 굳이 이걸 고치려고 했나?
말로 시킬 수 있는 비서라면 쓸 만하겠다고 봤다. 다만 그 말이 한국어여야 쓸모가 생긴다.
며칠 전 스트림독 사이트를 보다가 AI 봇이 들어온 걸 알게 돼서 뭘 할 수 있나 점검부터 했다. 기능 자체는 익숙했다. MCP 서버를 붙여 도구를 늘리는 구조라 파일 검색이나 유튜브 열기, 구글 검색 같은 걸 시킬 수 있었다. 요즘 에이전트 봇이 다 하는 일이다. 나중에 클로드와 함께 코드를 뜯어보니 내장 MCP 서버가 물려 있었고 로그에도 파일 검색이 돌아간 기록이 남아 있었다. 여기까지만 보면 그냥 에이전트가 하나 더 늘어난 셈이다.
내가 다르게 본 지점은 입력이 대화라는 데 있었다. 창을 띄우고 타이핑하는 대신 하던 일을 멈추지 않고 말로 시킬 수 있다. “그 파일 어디 있더라” 정도는 손 대지 않고 그냥 입으로 물어보면 된다. 이만큼만 되어도 검색하다 끊기는 흐름이 확 줄어든다.
그래서 한국어가 되는지부터 봤는데 안 됐다. 타이핑은 되고 마이크만 안 됐다. 말로 시키는 게 유일한 장점인 물건이 그 말을 못 알아들으면 쓸 이유가 없어진다. 그래서 붙잡고 늘어졌다.
미리 말해두면 스트림독 Komi 한국어 음성 인식은 앱 설정으로는 절대 못 고치는 문제였다. 그래서 앱을 뜯어 음성 인식 부분만 내 로컬 Whisper로 바꿔 꽂았다.
다만 이 글에서 하고 싶은 얘기는 Komi 자체가 아니다. 난독화된 Electron 앱을 어떻게 뜯고 어디를 고쳐야 할지 어떻게 찾아내는지가 본론이다. 나도 클로드와 같이 작업했고, 코드를 직접 읽지는 않았다. 어디를 보라고 시킬 줄만 알면 되니 누구나 할 수 있다고 본다.
이 글에 나오는 용어 4개
STT(ASR) — 음성을 텍스트로 바꾸는 단계. Speech To Text라고 한다. 마이크와 LLM 사이에 있다.
웨이크워드 — “Hi Komi”처럼 앱을 깨우는 호출어만 알아듣는 작은 모델. 문장은 못 받아적는다.
app.asar — Electron 앱이 자기 소스코드를 전부 담아두는 아카이브 파일.
난독화 — 변수명과 문자열을 _0x3d0d2c(0x758) 같은 꼴로 바꿔 읽기 어렵게 만든 상태.
스트림독 Komi 한국어, 왜 타이핑은 되는데 음성만 안 될까?
음성 인식을 MiraBox 서버가 맡고 있는데, 그 서버가 한국어를 지원 언어로 안 쳐주기 때문으로 보인다. 앱이 아니라 서버 쪽 문제라 사용자가 손댈 구석이 없다.
Komi는 입력 방식이 두 가지다. 채팅창에 타이핑하거나, “Hi Komi”로 깨워서 말하거나. 타이핑은 한국어가 완벽하다. 그런데 말로 하면 중국어 아니면 일본어로 찍힌다. 어쩌면 전부 중국어일 수도 있겠다. “오늘 날씨 어때”라고 하면 화면에 한자나 가나가 뜨고 대답도 당연히 엉뚱하게 돌아온다.
처음에는 딥시크 차단설을 의심했다. 중국 회사 제품인 데다 국내에서 딥시크가 한때 시끄러웠던 기억이 있어서다. 두 가지가 다 틀렸다. 우선 개인 사용자 기준으로는 딥시크 접속이 막힌 적이 없다. 기관이나 회사가 자기네 망에서 차단했을 뿐이다. 그리고 코드를 까보니 채팅 LLM 기본값이 실제로 DeepSeek 계열이긴 한데, 정작 채팅은 멀쩡히 되고 있었다.
여기서 첫 번째 판별 기준을 얻었다. 타이핑은 되는데 음성만 안 되면 STT를 의심하면 된다. 타이핑한 글자는 STT를 안 거치고 LLM으로 바로 간다. 그러니 LLM은 무죄고, 범인은 그 앞단에 있다고 봤다.
설정으로는 왜 못 고치나?
음성 언어를 고르는 항목이 아예 없고, 설정에 있는 커스텀 서버 주소는 채팅 경로만 바꾸기 때문이다.
당연히 설정부터 뒤졌다. Space 계정에 로그인하라길래 했고, 앱 언어를 한국어로 바꿔봤고, 속성 패널도 끝까지 내려봤다. 음성 언어를 건드리는 항목이 없다.
설정에 커스텀 API URL 입력란이 있길래 기대했는데, 나중에 코드를 보니 /service/chat/v1, 그러니까 채팅 경로에만 먹는 값이었다. 음성 인식 요청이 나가는 주소는 따로 박혀 있어서 설정과 아무 상관이 없다. 이 구조를 모르면 커스텀 URL을 아무리 바꿔봐도 증상이 왜 그대로인지 영영 못 찾는다.
게다가 앱 설명에 “onnx 모델로 마이크 인식”이라는 문구가 있었다. 나는 이걸 보고 로컬 STT 모델의 언어 설정이 문제겠거니 하고 그쪽부터 손대게 했는데, 결과적으로 헛다리였다. 여기서 한 시간을 날렸다.
남은 선택지는 둘이었다. MiraBox 디스코드에 문의하고 기다리거나, 직접 까보거나. 그래서 직접 까봤다.
난독화된 Electron 앱, 어떻게 뜯어보나?
여기서부터가 이 글의 핵심이다. Komi는 스트림독 플러그인으로 설치되지만 알맹이는 Electron 앱이다. Electron 앱은 뜯기 쉽다. 내부 코드가 app.asar 하나에 들어 있고 공식 도구로 풀린다.
안 해도 되는 것부터 짚고 가겠다. 폴더를 통째로 AI에 올릴 필요가 없다. 이 플러그인 폴더가 559MB인데, 정작 들여다본 건 파일 두 개, 합쳐서 5.6MB였다. 폴더의 1%다.
1단계 — 파일 목록만 뽑는다 (업로드 0)
윈도우 기준 경로는 여기다.
dir /s /b /o-s "%APPDATA%\HotSpot\StreamDock\plugins\com.komi.sdPlugin\" > list.txt
크기순으로 정렬된 목록 파일 하나가 생긴다. 아직 아무것도 올리지 않았는데 다음 단계의 결론이 이 목록만으로 나온다.
💬 AI에게 이렇게 시키면 된다
“이 플러그인의 파일 목록이야. 마이크 음성 인식이 로컬에서 도는지 서버로 나가는지, 파일 구성만 보고 판단해줘. onnx 모델이 있다는데 그게 뭘 하는 크기인지도.”
2단계 — 크기순으로 늘어놓으면 절반은 답이 나온다
폴더 전체 559 MB
win-unpacked/resources/app.asar 230,915,428 바이트 ← 앱 코드 전부
win-unpacked/Komi.exe 223,053,840 바이트 ← Electron 런타임
win-unpacked/.../resources/native/onnxruntime.dll 14,677,528 바이트 ← onnx 실행기
win-unpacked/.../resources/wakeword/
encoder-...int8.onnx 4,600,657 바이트
decoder-...onnx 700 KB 남짓
joiner-...int8.onnx 300 KB 남짓
onnx 파일이 딱 세 개인데 폴더 이름이 wakeword다. 셋을 합쳐도 6MB 남짓이다. “Hi Komi”를 알아듣는 웨이크워드 모델이지 음성 인식 모델이 아니라는 뜻이다.
기준을 하나 기억해두면 두고두고 쓸 만하다. 문장을 통째로 받아적는 STT 모델은 아무리 작아도 수십 MB고, 정확도를 챙기면 수백 MB에서 1GB를 넘긴다. 이 글에서 쓰는 large-v3만 해도 3GB에 가깝다. 6MB짜리 onnx로 문장을 받아적지는 못한다. 그러니 진짜 음성 인식은 다른 데서 돌고 있고, 남는 가능성은 서버 호출뿐이다.

3단계 — asar에서 필요한 파일 두 개만 꺼낸다
Node.js가 깔려 있으면 목록부터 뽑는다. 여기서도 아직 아무것도 올리지 않는다.
npx -y @electron/asar list app.asar > files.txt
목록을 보면 out/main/index.js(메인 프로세스)와 out/renderer/assets/index-XXXX.js(화면 쪽)가 눈에 들어온다. extract-file을 쓰면 아카이브에서 파일 하나만 꺼낼 수 있다. 230MB를 다 풀 이유가 없다.
npx -y @electron/asar extract-file app.asar out/main/index.js
npx -y @electron/asar extract-file app.asar out/renderer/assets/index-COe1bGDE.js
실제로 올린 것
out/main/index.js — 431,946바이트. 서버 주소와 로그인 관련 상수가 들어 있다.
out/renderer/assets/index-COe1bGDE.js — 5,498,184바이트. 마이크 녹음과 음성 인식 호출, 나중에 고칠 세 군데가 전부 여기 있다.
둘을 합쳐 5.6MB다. 다만 난독화된 문자열을 풀려면 이 파일들은 통째로 있어야 한다. 문자열 배열과 디코더 함수가 파일 곳곳에 흩어져 있어서 앞뒤 몇 줄만 잘라 올리면 복호화가 안 된다. 그래도 파일 하나 올리는 것과 폴더를 통째로 올리는 것은 얘기가 다르다.
이 폴더에는 9.6MB짜리 런타임 로그도 쌓여 있었다. 열어보니 MCP 세션 오류만 4,000줄 넘게 반복될 뿐 대화 내용이나 인증 토큰은 없었다. 그래도 굳이 올릴 이유는 없는 파일이다. 앱마다 다르니 통째로 올리기 전에 로그와 설정 파일에 뭐가 들었는지는 한 번 보고 가는 게 좋다.
꺼낸 파일을 열어보면 이렇게 생겼다.
_0x10f408[_0x3d0d2c(0x1850)](_0x3d0d2c(0x758),_0x192c28,_0x1c8e6b(_0x192c28))
사람이 읽기는 힘들다. 그렇다고 포기할 것도 없다. 우회로가 둘 있다.
우회로 A — 살아남은 이름을 검색한다. 난독화 도구가 모든 걸 숨기지는 못한다. 특히 상수 이름은 그대로 남는다. out/main/index.js를 열자마자 이런 줄이 나왔다.
PRIMARY_API_ORIGIN='https://ai.key123.vip:7999',
FALLBACK_API_ORIGIN='https://ai.miraboxeco.com',
CHAT_API_SUFFIX=...
LOGIN_PAGE='https://space.key123.vip/login',
USER_INFO_URL='https://space.key123.vip/interface/user/info'
이름도 평문이고 값도 평문이다. 이 다섯 줄만 봐도 이 앱이 외부 서버에 붙는다는 게 확정된다. 여기까지 오는 데 0.4MB짜리 파일 하나면 충분했다.
⚠️ 검색이 안 걸리는 함정
분명히 파일 안에 있는 문구인데 검색하면 0건이 뜨는 일이 있다. 띄어쓰기 때문이다. 난독화 도구가 문자열 안의 띄어쓰기를 눈에 안 보이는 기호로 바꿔놓는다. 그래서 화면에 ASR transcribe expects a Blob으로 보이는 에러 메시지가 파일에는 ASR\x20transcribe\x20expects\x20a\x20Blob으로 저장돼 있다. \x20이 띄어쓰기 자리를 대신 차지한 기호다.
그러니 띄어쓰기가 든 문구로 찾으면 안 걸린다. transcribe처럼 붙어 있는 단어 하나로 찾아야 한다. 이거 하나 모르면 “문자열이 다 숨겨졌구나” 하고 포기하기 딱 좋다.
우회로 B — 문자열 복호화 함수를 그냥 실행해버린다. _0x3d0d2c(0x758)은 문자열 배열의 0x758번째를 꺼내라는 뜻이다. 그 배열과 디코더 함수가 같은 파일 안에 있으니, Node로 그 함수만 떼어 실행하면 원래 문자열이 나온다. 직접 할 것 없이 AI에게 시키면 된다.
💬 AI에게 이렇게 시키면 된다
“이 JS 파일은 javascript-obfuscator로 난독화돼 있어. 문자열 배열과 디코더 함수를 뽑아서 node로 실행하고, 0x758, 0x18bf, 0x11ff가 각각 무슨 문자열인지 알려줘.”
결과는 이랬다.
0x758 → "file"
0x18bf → "/service/asr/api/v1/transcription/transcribe"
0x11ff → "language"

이제 그림이 맞춰진다. Komi는 마이크 녹음을 file 필드에 담고 language 값과 Authorization: Bearer 토큰을 붙여서 앞에서 본 그 서버의 ASR 엔드포인트로 올려보낸다. 로컬에서 처리하는 게 아니었다. 주소는 PRIMARY_API_ORIGIN을 먼저 쓰고 실패하면 FALLBACK_API_ORIGIN으로 넘어간다.
4단계 — 언어값을 정하는 코드 한 줄을 찾는다
language를 어디서 정하나 따라갔더니 이 한 줄이 나왔다.
return /[一-鿿]/.test(text) ? 'zh' : 'en'; // 한자 범위
앱 UI 번역 문자열에 한자가 섞여 있으면 zh, 아니면 en. 그게 전부다. 선택지가 둘뿐이라 ko는 아예 없다. 한국어 UI에는 한자가 없으니 그동안 계속 en이 나가고 있었다.
ko로 고정했는데 왜 그대로였나?
서버가 클라이언트에서 보낸 언어값을 무시하고 자기가 알아서 감지하는 것으로 보인다. 코드로 확인한 게 아니라 정황을 보고 내린 추정이다. 근거는 두 가지다.
- Whisper 계열에 영어를 강제하면 한국어 음성은 대개 영어 단어로 나온다. 중국어나 일본어가 나올 이유가 없다. 그런데 실제로는 한자와 가나가 찍혔다. 보낸
en이 안 먹혔다는 얘기다. ko로 고정해서 보내도록 먼저 고쳐봤는데 결과가 똑같았다. 클라이언트를 아무리 손봐도 안 변하면 남는 건 서버뿐이다.
응답을 처리하는 쪽에 중국어 환각 문구를 지우는 정규식도 들어 있었다. 무음이나 잡음 구간에서 Whisper가 없는 말을 지어내는 현상은 익히 알려져 있으니, 그걸 걸러내려고 넣어둔 코드로 보인다. 서버가 Whisper 계열이라는 정황은 되지만 확정할 수는 없다.
Whisper의 자동 감지가 2~3초짜리 짧은 한국어를 일본어나 중국어로 잘못 읽는 것도 유명한 얘기다. 증상이 딱 그거였다.
결국 사용자가 고칠 수 있는 문제가 아니다. 그래서 방향을 틀었다. 서버를 못 고치면 서버를 바꾸면 된다.

로컬 Whisper를 어떻게 연결하나?
앱이 보내는 요청 규격과 내 서버가 받는 규격만 맞추면 된다. 나는 영상 자막용으로 n8n 스택에 faster-whisper large-v3 컨테이너를 이미 돌리고 있었다. Flask로 감싼 단순한 API인데, 이걸 Komi의 STT 서버로 쓰기로 했다.
맞춰야 할 항목을 표로 정리하면 이렇다.
| 항목 | Komi가 보내는 것 | 내 Whisper 서버 | 조치 |
|---|---|---|---|
| URL | MiraBox 서버 /service/asr/…/transcribe | 127.0.0.1:5055/transcribe | asar 패치 |
| 파일 필드명 | file |
audio |
asar 패치 |
| 언어 | en (한국어 UI일 때) |
받은 값 그대로 강제 | asar 패치 → ko |
| 오디오 포맷 | webm (MediaRecorder) | ffmpeg로 디코딩해서 다 받음 | 없음 |
| 응답 | JSON의 text 키 |
text 키 있음 |
없음 |
| CORS | file:// 출처 + Bearer 헤더 | 없었음 | Flask에 헤더 추가 |
손댈 곳은 앱에서 세 줄, 서버에서 열 줄이다. 중간에 프록시 서버를 세울 것도 없었다.
앱 쪽 — app.asar를 길이 그대로 덮어쓴다
asar를 풀어서 고치고 다시 묶는 게 정석이다. 그런데 이 앱은 네이티브 모듈이 app.asar.unpacked로 따로 빠져 있어서, 다시 묶을 때 그 구조를 그대로 재현해야 한다. 번거롭다.
그래서 파일 길이는 건드리지 않고 그 자리만 덮어쓰는 쪽으로 갔다. 바꿔 넣을 문자열이 원본보다 짧으면 남는 자리를 공백으로 채운다. JS에서 공백은 아무 의미가 없어서 안전하고, asar 헤더도 손댈 필요가 없다.
스크립트로 만들어뒀다. 원본 app.asar에 돌리면 백업을 만들고 패치본을 뱉는다.
python patch_komi_asar.py "%APPDATA%\HotSpot\StreamDock\plugins\com.komi.sdPlugin\win-unpacked\resources\app.asar"
OK 언어 판정 → 고정값 @ offset 170803846
OK ASR 엔드포인트 URL @ offset 171968448
OK 업로드 필드명 file → 원하는 이름 @ offset 171969143
백업: app.asar.bak
완료: app.asar.patched
Whisper 서버 주소나 필드명이 다르면 --url, --field 옵션으로 바꾸면 된다. 스크립트는 패턴이 정확히 한 번 발견될 때만 진행하고 아니면 알아서 멈춘다. 플러그인 버전이 바뀌면 여기서 걸린다.
⚠️ 이 방법이 통한 전제 조건
Electron에는 asar 무결성 검증 기능(fuse)이 있어서, 켜져 있으면 고친 asar를 앱이 거부한다. Komi.exe의 fuse 설정을 읽어보니 101100011이었고 무결성 검증에 해당하는 자리가 0, 그러니까 꺼져 있었다. 다른 Electron 앱에 같은 수를 쓰려면 이것부터 봐야 한다. AI에게 “이 exe의 Electron fuse 설정 읽어줘”라고 시키면 된다.
서버 쪽 — Flask에 CORS를 연다
Komi 화면은 file://로 뜨는 Electron 렌더러다. 브라우저 보안 규칙이 그대로 걸려서, 서버가 CORS 헤더를 안 주면 응답이 도착해도 JS가 못 읽는다. 게다가 Komi가 Authorization 헤더를 붙이는 탓에 preflight(OPTIONS) 요청이 먼저 온다. Flask에 이것만 얹었다.
@app.after_request
def add_cors_headers(resp):
resp.headers["Access-Control-Allow-Origin"] = "*"
resp.headers["Access-Control-Allow-Headers"] = "Authorization, Content-Type"
resp.headers["Access-Control-Allow-Methods"] = "POST, OPTIONS"
return resp
@app.route("/transcribe", methods=["OPTIONS"])
def transcribe_preflight():
return ("", 204)
추가 패키지 없이 Flask 기본 기능으로 된다. 인식 로직은 한 줄도 안 건드렸다. docker compose up -d --build whisper-large-v3로 다시 띄우고 /health 응답만 확인하면 된다.
적용과 확인
- 스트림독을 끄고, 작업관리자에서
Komi.exe가 정말 죽었는지 확인한다. 이걸 빠뜨리면 파일을 바꿔도 예전 게 계속 돈다. app.asar.patched를app.asar로 이름을 바꿔 원래 자리에 넣는다.- 스트림독을 실행하고 “Hi Komi”로 깨운 다음 한국어로 말한다.
docker logs -f whisper-large-v3에OPTIONS 204와POST /transcribe 200이 찍히면 성공이다.
됐다. 이제 한국어로 말하면 한국어로 받아적고 한국어로 대답한다. large-v3라 인식 정확도는 원래 MiraBox 서버보다 오히려 낫다.
다른 앱에도 쓸 수 있나?
구조가 비슷하면 그대로 쓸 수 있다. 이번에 삽질하면서 건진 판단 기준이 다섯 개다.
0. 올리기 전에 목록부터 본다. 파일 목록과 크기만으로 판정되는 질문이 생각보다 많다. 이번에도 목록 한 장으로 “STT는 서버에 있다”까지 나왔다. AI에 올린 건 폴더의 1%였다.
1. “로컬 모델 씁니다”라는 문구에 낚이지 말 것. 앱이 로컬 모델을 쓴다고 해서 핵심 기능까지 로컬인 건 아니다. 파일 크기만 봐도 안다. 6MB짜리 onnx는 웨이크워드지 STT가 아니다.
2. 난독화는 벽이 아니라 문턱이다. 에러 메시지나 정규식, URL 조각은 살아남고 디코더 함수도 같은 파일 안에 있다. AI에게 시키면 30분이면 뚫린다. 직접 읽으려 하지 말고 “이 숫자가 무슨 문자열이냐”를 물어보면 된다.
3. 첫 패치가 안 먹으면 코드가 아니라 가설을 의심하라. ko 패치가 안 통했을 때 나는 코드 캐시를 의심하며 시간을 버렸다. 진짜 이유는 서버가 값을 무시하는 데 있었다. 영어를 강제했는데 중국어가 나온다는 사실 자체가 처음부터 힌트였는데 그냥 지나쳤다.
4. 서버를 못 고치면 서버를 바꾼다. 요청과 응답 규격만 맞으면 상대가 누구든 상관없다. 이미 돌리던 컨테이너가 있어서 여기서부터는 30분 만에 끝났다. 로컬 AI 인프라를 하나 세워두면 이런 데서 계속 써먹는다.
주의할 점
- 플러그인이 업데이트되면 원래대로 돌아간다. asar가 덮어써지니 다시 고쳐야 한다. 버전이 바뀌면 코드 위치도 달라져서 스크립트 패턴이 안 맞을 수 있다. 그럴 땐 다시 뜯어야 한다.
- Whisper 컨테이너가 죽으면 Komi 음성도 같이 죽는다.
restart: unless-stopped로 걸어두고, 갑자기 안 되면docker ps부터 확인한다. - large-v3는 VRAM을 계속 물고 있는다. 내 5070 Ti 16GB에서는 ComfyUI와 같이 돌리면 빠듯하다. 가볍게 가려면
medium이나turbo로 내려도 한국어는 충분하다. - 채팅 LLM은 여전히 MiraBox 서버로 간다. 바꾼 건 STT뿐이라 대화 내용이 밖으로 나가는 구조는 그대로다.
자주 묻는 질문
Q. faster-whisper 말고 다른 Whisper 서버를 쓰는데 되나?
규격만 맞추면 된다. 파일 필드명이 file이면 --field file, 엔드포인트가 다르면 --url로 바꾼다. 응답에 text 키가 없는 서버라면 중간에 작은 프록시를 두고 변환하면 된다. FastAPI로 20줄이면 된다. OpenAI 호환 서버(/v1/audio/transcriptions)는 model 필드를 요구하기도 해서 이때도 프록시가 필요할 수 있다.
Q. 앱 폴더를 통째로 AI에 올려도 괜찮나?
올릴 필요가 없다. 이번 건도 559MB 중에 5.6MB면 됐다. 목록부터 보고(업로드 0), 상수와 URL을 볼 메인 파일 하나를 올리고, 그래도 안 풀리면 렌더러 번들 하나를 더 올리는 순서면 충분하다. 앱 폴더에는 로그와 설정이 같이 들어 있고, 앱에 따라 인증 토큰이나 사용 기록이 남아 있을 수도 있다. Komi는 확인해보니 로그에 토큰도 대화 내용도 없었지만, 이 앱이 그랬다는 것이지 다른 앱도 그렇다는 보장은 없다. 파일을 아예 밖으로 안 내보내고 싶으면 Claude Code처럼 내 PC 폴더를 직접 읽는 도구를 쓰면 된다.
Q. Whisper 서버가 다른 PC에 있어도 되나?
된다. --url에 그 PC 주소를 넣으면 된다. 다만 Komi가 file:// 출처로 요청하니 CORS 헤더는 반드시 있어야 하고, 방화벽에서 해당 포트도 열어줘야 한다.
Q. 맥에서도 되나?
플러그인 폴더에 mac-arm64가 따로 있어서 asar 위치가 다르다(~/Library/Application Support/HotSpot/StreamDock/plugins/…). 고칠 내용은 같을 가능성이 높지만 실제로 해보지는 않았다. 렌더러 번들이 같으면 스크립트가 그대로 먹고, 다르면 패턴이 안 맞아서 멈춘다.
Q. 이거 약관 위반 아닌가?
내 PC에 깔린 앱을 내가 고쳐서 내가 쓰는 것이다. 고친 파일을 재배포하지도 않았다. 그래서 asar 대신 스크립트만 공개한다. MiraBox 서버 입장에서는 오히려 요청이 줄어든다. 다만 공식 지원은 당연히 못 받으니 알아서 책임지는 영역이다.
Q. MiraBox에 그냥 한국어 지원을 요청하면 안 되나?
해야 한다. 서버가 language 값을 존중하거나 자동 감지 후보에 한국어를 넣어주는 게 근본 해결이다. 디스코드(discord.mirabox.net)에 엔드포인트 경로와 증상을 적어 올리면 개발자가 바로 알아볼 수준의 정보다. 이 글은 그게 반영될 때까지 쓰는 우회로다.
첨부 파일
두 파일과 사용법을 zip 하나로 묶어 올려뒀다.
📦 komi-whisper-files.zip 내려받기 (13KB)
patch_komi_asar.py — app.asar 패치 스크립트. --url, --field, --lang 옵션을 지정할 수 있다.
app.py — CORS 처리를 넣은 faster-whisper Flask 서버. 이미 Whisper 서버를 돌리고 있다면 이 파일로 갈아끼울 것 없이 위의 CORS 블록만 옮겨 붙이면 된다.
README.txt — 사용법과 주의사항.
패치한 app.asar 자체는 올리지 않는다. 각자 자기 PC에 깔린 앱에 스크립트를 돌리는 게 맞다.