WIKInoon-desktop
개인 제품·2026.06 ~ (프로토타입)·기획·개발 단독

noon-desktop

워치 noon의 눈을 얹은 macOS 반려 AI.

SwiftAppKitSpriteKitClaudeSwiftWhisperSpeech

noon-desktop은?

macOS 데스크톱에 상주하는 살아있는 눈 + AI 두뇌다. 워치 noon의 눈(외계 신호 글리치)을 데스크톱으로 가져오되, 그 위에 대화(Claude) · 듣기(로컬 STT) · 말하기(TTS) · 도구 그라운딩(전문가 모드)을 얹었다. AppKit + SpriteKit 네이티브.

작동하는 프로토타입(v0.1)이다. 출시 제품이 아니라 혼자 쓰려고 만든, 눈·대화·듣기·말하기·도구·홀로그램 카드가 실제로 도는 상태까지 온 실험이다. 워치 noon과는 눈/글리치 렌더 DNA만 공유하고 비전이 다른 별개 프로젝트다.

noon-desktop macOS
데모 준비중

호출 → 듣기 → 대화 → 표정 → 홀로그램 카드

왜 만들었나

워치 noon이 "반응형 존재"라면, 데스크톱은 그 위에 자발성과 쓸모를 얹어 "먼저 반응하고 도와주는 동료"를 실험한 것이다. 1인 시대의 반려 AI를 만들되, 감성만이 아니라 실제 쓸모까지 얹고 싶었다.

쓸모의 방향은 범용 비서가 아니다. "전문가 모드 = 프롬프트 + 전용 도구" 라는 방향으로, 특정 영역에 깊게 들어가고 할루시네이션 대신 라이브 위키·로컬 공식 데이터에 근거하게 했다. 음성 인식은 로컬로 돌려 네트워크 없이 동작하게 했다. 프라이버시와 오프라인을 위해서다.

전체 구조

전역 핫키 · 커서 · 음성
CGEventTap · 마우스 · 마이크
NoonEars (하이브리드 STT)
실시간 자막 + 로컬 정확 받아쓰기
NoonBrain (Claude)
structured output → {표정, 말}
도구
look_at_screen · get_weather · minecraft 위키/레시피
표정 결착 + NoonVoice
눈 표정 enum + TTS + barge-in
홀로그램 빔 카드
눈에서 빔 → 날씨·레시피 카드가 맺힘

AI 두뇌와 도구

워치와 기술 축이 완전히 다르다. 여긴 대화·음성·도구 그라운딩이 핵심이다.

3
전문가 모드 (마인크래프트 등)
782
로컬 공식 레시피
4
대화 모드 (off·active·ambient·call)
~800ms
위키 라이브 검색 (1콜)
2
성격 (다정·츤데레)
0
클라우드 STT (전부 로컬)
NoonBrain — 응답을 눈 표정에 결착
전문가 모드 = 프롬프트 + 전용 도구
위키 라이브 그라운딩 (할루시네이션 회피)
하이브리드 로컬 STT (프라이버시)
말하기 + barge-in (끼어들기)
눈에서 쏘는 홀로그램 빔 카드
전역 핫키 · 커서 추적 상주 창

NoonBrain, 표정 있는 대화

Claude와 대화하되, 범용 SDK 없이 raw HTTPS로 /v1/messages를 직접 친다. 핵심은 structured output이다. 응답을 {expression, say} JSON 스키마로 강제해서, 모델이 고른 표정(idle·happy·surprised·curious·sleepy·thinking)이 곧바로 눈 표정과 1:1로 결착된다. 그래서 "표정 있는 대화"가 된다.

페르소나는 정체성 + 성격 + 전문가를 독립적으로 합성한다. 성격은 다정(warm)·츤데레(sassy) 2종, 전문가는 none·minecraft·coding 3종으로, 말투와 지식을 따로 조립한다. 화면을 곁눈질하는 비전 호출은 환각을 막으려 Sonnet으로 고정했고, 프롬프트가 "화면 설명·중계 금지(설명충 최악)"를 강조한다.

전문가 모드 = 프롬프트 + 전용 도구

이 프로젝트의 차별점. 전문가 모드는 단순 프롬프트가 아니라 그 모드에서만 노출되는 전용 도구를 얹는다. 마인크래프트 모드일 때만 search_minecraft_wikishow_crafting_recipe가 추가된다.

  • Tier1은 라이브 위키 검색이다. minecraft.wiki의 MediaWiki API를 단일 호출로 검색+본문 추출까지 한다(2콜→1콜로 ~800ms 단축). 지식을 실시간 그라운딩해 할루시네이션을 피한다.
  • Tier2는 로컬 공식 데이터다. 크래프팅 레시피 782개를 번들 JSON으로 넣었다(공식 minecraft-data + Mojang 한국어). 3×3 격자는 "모델이 추측하지 말고, 시스템이 공식 데이터로 채운다"는 원칙이다. 못 찾으면 추측 대신 되묻는다.

MCP를 쓰지 않고 자체 tool-use 스키마를 택했다(가용한 MCP는 대부분 봇 조종용이라 맞지 않았다).

스크린샷 준비중

전문가 모드 — 마인크래프트 레시피 홀로그램 카드

하이브리드 로컬 STT

말하는 동안엔 실시간 자막을, 문장이 끝나면 정확한 받아쓰기를 뽑는다. 둘 다 로컬로 돈다. 말하는 중엔 Apple SFSpeechRecognizer(on-device 강제)가 부분 자막을 뿌리고, 침묵이 감지되면(VAD) SwiftWhisper(로컬 ggml-medium)가 정확본을 뽑는다.

환각 필터가 중요했다. Whisper가 조용할 때 "시청해주셔서 감사합니다 / 구독 / 좋아요" 같은 유튜브 환각을 뱉는 걸 차단한다. 한계도 있다. STT 모델(약 1.5GB)은 번들에 못 넣어 수동 배치해야 하고, Core ML 인코더가 없어 CPU 추론이 느리다(설정으로 상쇄).

말하기 + barge-in

시스템 TTS 또는 Google Cloud TTS로 말한다. 말하는 동안엔 자기 목소리를 무시하고, "잠깐·그만·멈춰" 같은 stopWord가 들리면 즉시 끊는다(barge-in). 우상단엔 절차적으로 그린 "통화 중" 알약(CallIndicator)이 뜬다.

눈·글리치 데스크톱 재구성 + 커서 추적

워치의 눈을 그대로 이식하지 않고, 정체성 값과 형태만 빌려 데스크톱용으로 새로 구성했다. 글리치 셰이더는 워치와 같은 결정(un-premultiply로 검은 테두리 제거)을 공유하되 소스는 별개다. 데스크톱에만 있는 생명력도 넣었다. 마우스 커서를 따라가는 시선, 자율 사케이드, 오래 자리를 비웠다 오면 반기는 졸음/깨어남, 실제 날씨가 비면 눈 안에 절차적으로 내리는 비.

데스크톱 UX

  • 전역 핫키: CGEventTap으로 [ 듣기 시작 / ] 종료. (NSEvent 전역 모니터가 키를 안 줘서 입력 모니터링 권한이 필요.)
  • 상주 창: borderless·투명·전 스페이스에 뜨는 FloatingEyeWindow. Dock 아이콘 없이 상태바 "◉" 메뉴로만. 안쪽 드래그로 이동, 바깥 링으로 리사이즈.
  • 4가지 대화 모드: off / active(적극 듣기+자막) / ambient(상시 잠복, 별칭 불러야 반응) / call(통화처럼 다 듣기).
  • 홀로그램 빔 카드: 눈에서 볼류메트릭 광선을 쏴 날씨·레시피 카드가 홀로그램처럼 맺히는 연출. 카드는 SF Symbol·이모지 없이 매트릭스 그린으로 절차 드로잉한다. noon 리소스 규칙 그대로다.

기술 스택

  • 플랫폼: Swift 5.9 · AppKit · macOS 13+ · 독립 SPM 패키지
  • AI: Anthropic /v1/messages (structured output) · claude-sonnet-4-6
  • 음성: SFSpeechRecognizer(on-device) · SwiftWhisper(로컬 whisper.cpp) · AVFoundation TTS
  • 도구 그라운딩: minecraft.wiki MediaWiki API · 로컬 레시피 782 · 기상청 KMA 직접
  • 연출: SpriteKit(눈·셰이더) · CoreGraphics/QuartzCore(빔·홀로그램)

왜 이 스택

  • AppKit + SpriteKit: 데스크톱 컴패니언이라 macOS 네이티브로. 워치 noon의 눈 렌더(SpriteKit)를 그대로 가져와 표정을 재사용했다.
  • Claude structured output: 응답을 자유 텍스트가 아니라 구조화 출력으로 강제해서, 답과 함께 눈 표정을 결착했다. 파싱이 흔들리지 않고 "표정 있는 대화"가 된다.
  • 하이브리드 로컬 STT (SFSpeechRecognizer + SwiftWhisper): 음성을 클라우드로 보내지 않으려고 온디바이스로 갔다. 프라이버시·오프라인이 반려 AI의 전제라고 봤다.
  • 자체 tool-use 스키마: 위키 라이브 검색 같은 전용 도구를 붙였는데, 공개 MCP 서버는 대부분 봇 조종용이라 안 맞았다. 그래서 도구 그라운딩을 자체 tool-use로 짰다.

Trouble Shooting

  • CPU 추론이 느림. Whisper Core ML 인코더가 없어서 그렇다. audio_ctx 축소·single-segment로 상쇄했다.
  • STT 모델 미번들. 1.5GB라 번들에 못 넣는다. 수동 배치해야 하고, 없으면 음성 인식이 조용히 침묵한다.
  • 머신 종속. 프로토타입이라 Info.plist 절대경로·환경 폴백 경로가 하드코딩돼 다른 머신엔 그대로 안 옮겨진다.
  • 모델 주석 스테일. 코드 주석은 "Haiku"라 하지만 실제 기본은 Sonnet이다.

Decision Log

Decision #01전문가 모드 = 프롬프트 + 전용 도구

범용 비서 대신 특정 영역에 깊게. 프롬프트만 바꾸는 게 아니라, 그 모드에서만 노출되는 전용 도구(위키·레시피)를 함께 얹는다.

Decision #02위키 라이브 그라운딩으로 할루시네이션 회피

지식은 minecraft.wiki 실시간 검색, 레시피는 로컬 공식 데이터 782개. 격자는 모델이 추측하지 못하게 시스템이 공식 데이터로 채운다. 못 찾으면 지어내지 말고 되묻는다.

Decision #03structured output으로 대화를 표정에 결착

응답을 {expression, say} 스키마로 강제해, 모델이 고른 표정이 곧바로 눈 표정이 되게. "표정 있는 대화"의 핵심 장치.

Decision #04음성 인식을 전부 로컬로

SwiftWhisper 로컬 + Apple on-device 인식으로 네트워크 없이 동작한다. 프라이버시와 오프라인을 위해서다. 클라우드 STT 0.

Decision #05MCP 대신 자체 tool-use 스키마

가용한 MCP 서버가 대부분 봇 조종용이라 이 용도에 맞지 않았다. Anthropic tool-use 스키마로 직접 정의했다.

Decision #06출시 코드와 분리된 독립 SPM

워치·폰 출시 코드와 완전히 분리한 프로토타입 패키지로 격리했다. 실험을 안전하게 돌리기 위해서다.

회고, 작동하는 프로토타입

혼자 쓰려고 만들었는데, 눈 + 대화 + 듣기 + 말하기 + 도구 그라운딩 + 홀로그램 카드가 실제로 도는 프로토타입까지 왔다. AppKit과 SpriteKit으로 네이티브하게, 로컬 STT와 structured output과 라이브 위키 그라운딩을 직접 엮었다. 이 기술 축은 워치 noon과 완전히 달라서 별개 이야기로 선다.

다음 우선순위는 존재감·자발성이다. 반응형을 넘어 화면과 상황에 먼저 곁눈질하는 "동료"로. 도구는 수치 데이터(Tier2)로 더 깊게. 출시 잡일은 "잘 풀리면" 후순위다. 지금까지 온 건 "1인 개발 프로토타입"이고, 축은 로컬-우선 AI 컴패니언이다.