플러그인·데스크탑 앱
48007b6의 실제 파일과 함수를 따라가며, 초보자가 자신의 작은 앱에 옮겨 쓸 수 있는 설계를 설명합니다.
(PC2005-cloud/dsh-pet · 스타 3,663 · 포크 266 · MIT · 2026-10-23 GitHub 기준)
dsh-pet은 투명 배경 영상을 재생하는 캐릭터에 클릭·드래그·이동·대화 기능을 붙인 데스크톱 유틸리티입니다. 브라우저에서는 DeepSeek Harness(이하 DSH) 화면 위의 떠 있는 요소로, 데스크톱에서는 테두리 없는 Electron 창으로 나타납니다. 여러 캐릭터를 동시에 띄우고 각각 크기와 표시 위치를 정할 수 있습니다.
같은 캐릭터 로직을 브라우저와 데스크톱에 연결하는 방법이 이 저장소의 가장 큰 학습 자산입니다. 캐릭터 그림을 그리는 법만 배우는 것이 아닙니다. 설정을 읽고, 행동을 선택하고, 입력을 계산하고, 두 환경에 결과를 전달하는 전체 흐름을 배웁니다.
애니메이션 파일은 배우의 동작이고, 설정은 대본입니다. 호스트는 공연 순서를 정하는 진행자이며, 브라우저와 Electron은 서로 다른 무대입니다. 무대의 장치는 달라도, 다음 동작을 고르는 규칙과 움직임 계산은 함께 씁니다.
여기에 AI를 연결하면 배우가 말을 만들 수 있습니다. 그러나 몸짓 자체는 미리 만들어 둔 영상입니다. 모델이 매 프레임 캐릭터 영상을 생성하는 구조는 아닙니다.
| 층 | 하는 일 | 처음 읽을 파일 |
|---|---|---|
| 보이는 몸 | 영상 교체, 클릭 반응, 이동, 말풍선 표시 | dsh-pet/src/client/pet.tsdsh-pet/runtime/electron-helper/sprite.js |
| 행동과 상태 | 설정 병합, 행동 요청 처리, 상태 저장, 모델 호출 | dsh-pet/src/host/index.tsdsh-pet/src/host/config.ts |
| 소재 제작 | 배경 제거, 크기 정규화, 투명 영상 인코딩 | scripts/chroma_step02.pyscripts/normalize_step03.py |
dsh-pet/src/host/는 플러그인의 핵심 로직을 뜻하며,
Electron의 메인 프로세스와 항상 별도 프로세스인 것은 아닙니다.DSH 안에서는 세션의 작업 상태와 잔액도 캐릭터에 연결합니다. DSH 없이 실행할 때는 작은 대체 컨텍스트가 필요한 서비스를 제공합니다. 다만 세션 연동과 AI 대화는 다른 조건을 갖습니다. 독립 AI 대화는 별도 설정으로 켤 수 있고, DSH 세션 이벤트는 DSH 호스트가 있어야 합니다.
2026-10-23 기준 GitHub Trending TypeScript 주간 12위에 선정됐고, 스타는 3,663개, 포크는 266개입니다. 생성일은 2026-08-14입니다. 이 수치가 특정 기능 때문에 올랐다는 인과관계는 확인할 수 없지만, 코드와 문서에서는 다음 세 가지 매력을 읽을 수 있습니다.
설정 파일의 pets[].display에는 web, desktop, both, none이 있습니다.
기본값은 both입니다. DSH 화면 안에서 쓰다가 데스크톱으로 옮길 수 있으며, 독립 CLI와 Windows 앱을 위한 진입점도 있습니다.
설치 방법이 한 가지에 묶이지 않는다는 점이 입문자에게 매력적입니다.
내장 재생용 WebM은 106개, 약 51.83 MiB입니다. 별도 펫 팩은
$DSH_HOME/dsh-pet/pet/종류-config.json과 $DSH_HOME/dsh-pet/pet/종류-animation/으로 구성됩니다.
설정의 인스턴스 id와 소재 위치인 assetRoot를 구분해, 여러 인스턴스가 같은 소재를 재사용할 수 있습니다.
작은 결과물을 빨리 확인하면서도, 그 뒤의 설정·통신·배포 설계까지 단계적으로 공부할 수 있습니다. AI 영상 제작용 프롬프트와 Python 처리 스크립트도 있어, 앱 개발과 미디어 파이프라인이 어떻게 만나는지 살펴보기 좋습니다.
/dsh-pet-7340 아래의 API로 말풍선·애니메이션·위치를 제어할 수 있습니다.
커밋 48007b6은 제3자 메뉴 등록 시 표시할 쪽을 지정하는 target을 다룹니다.
dsh-pet/src/host/menu.ts의 MenuRegistry.register()는 web / desktop / both를 검사하며,
생략하거나 null이면 기존 호출과 같은 both로 처리합니다.
아래 표는 특정 경쟁 제품의 성능 순위가 아니라 구현 방식별 선택 기준입니다. dsh-pet의 장점은 영상 자산·호스트 연동·여러 실행 형태가 함께 있다는 데 있습니다.
| 구현 방식 | 주로 얻는 것 | dsh-pet이 더 제공하는 것 / 부담 |
|---|---|---|
| 단순 GIF 위젯 | 한 동작을 표시하는 간단한 구성 | 행동 선택·물리·대화 연동을 제공하지만 코드와 설정이 더 많음 |
| 웹 전용 펫 | 페이지의 DOM과 입력 API 사용 | Electron 창으로 데스크톱에도 표시함. 별도 런타임과 창 관리가 필요 |
| 독립 데스크톱 펫 | 브라우저 밖에 캐릭터 표시 | DSH 세션·잔액·플러그인 API까지 연결하는 경로를 제공 |
| 직접 만든 Electron 앱 | 화면과 동작을 자유롭게 설계 | 투명 창·소재 체계·설정 병합을 참고할 수 있음. 기존 규약 이해는 필요 |
Electron을 쓰므로 파일 크기만 보고 가벼운 네이티브 위젯과 같은 자원 사용을 기대하면 안 됩니다. 저장소에 최소 RAM, 평균 CPU 사용률, 배터리 소모를 입증하는 벤치마크는 없습니다. 배우기 좋은 구조라는 판단과 성능이 우수하다는 판단은 별도로 해야 합니다.
주 패키지는 저장소 루트 바로 아래의 dsh-pet/package.json에 있으며 버전은 0.3.8입니다.
의존성에 적힌 허용 범위와 잠금 파일에 고정된 버전은 서로 다른 정보입니다.
예를 들어 TypeScript는 ^5.5.0으로 선언돼 있지만 dsh-pet/pnpm-lock.yaml에는 5.5.2로 고정돼 있습니다.
peerDependencies가 설계의 일부입니다.
React는 개발용 목록에도 있어 두 역할이 겹칩니다.| 기술 | 실제 버전·선언 | 역할과 근거 |
|---|---|---|
| Node.js | >=22.12.0 | engines.node. 파일·HTTP·자식 프로세스 관리 |
| Cordis | ^4.0.4 → 4.0.4 | @deepseek-ai/cordis. 의존성 주입과 효과 해제 |
| DSH 서비스 | 주로 ^0.2.0-rc.1 | dsh-host-webserver, dsh-credentials, dsh-llm 등. 잠금 파일의 dsh-llm은 0.2.0-rc.2 |
| AI 전송 SDK | openai 7.39.0@anthropic-ai/sdk 0.135.0 | dsh-pet/src/standalone/ai-transport.ts에서 독립 AI 요청을 전송 |
| Zod | 4.6.7 | dsh-pet/src/standalone/ai-config.ts의 AI 설정과 요청 경계 검증 |
| 내장 HTTP | node:http | dsh-pet/src/standalone/server.ts. Express 서버가 아닌 Node 기본 서버 |
일반적인 클라우드 백엔드나 데이터베이스는 필수 구성 요소가 아닙니다. 설정과 대화 기록은 로컬 파일에, 화면 동기화 상태는 메모리에 둡니다. DSH 호스트의 모델·잔액 서비스를 쓰거나 독립 AI 설정을 켜면 외부 요청이 발생할 수 있습니다.
| 기술 | 실제 버전·설정 | 역할과 근거 |
|---|---|---|
| React / React DOM | ^18.2.0 → 18.2.0 | 브라우저 펫과 설정 화면. dsh-pet/src/client/app.ts가 호스트의 require로 주입 |
| Electron | 다운로드 기본값 43.3.0 | dsh-pet/src/host/helper-process.ts와 dsh-pet/electron-builder.yml. 일반 npm 실행 의존성으로 고정한 방식과 다름 |
| DOM / 영상 API | HTMLVideoElement, fetch, requestAnimationFrame | 데스크톱의 dsh-pet/runtime/electron-helper/sprite.js는 직접 DOM을 조작. 모든 화면이 React인 것은 아님 |
| 투명 영상 | VP9-alpha WebM640×360 / 24fps | scripts/encode_thumbs.py의 출력 설정. HEVC-alpha MOV는 별도 경로 |
dsh-pet/tsconfig.json은 strict: true, moduleResolution: Bundler,
jsx: react-jsx, noUnusedLocals: true를 사용합니다.
다만 호스트 접점에는 ctx: any도 남아 있습니다. 엄격한 컴파일 옵션이 외부 서비스 전체의 타입 안전성을 자동으로 보장하지는 않습니다.
| 도구 | 선언 → 잠금 버전 | 무엇을 담당하나 |
|---|---|---|
| TypeScript | ^5.5.0 → 5.5.2 | 타입 검사와 타입 선언 생성 |
| tsdown / Rolldown | ^0.8.0 → 0.8.01.0.0-beta.7 | 네 진입점과 데스크톱 공유 코어 묶기 |
| electron-builder | ^26.15.3 → 26.15.3 | Windows 배포 폴더 생성, ASAR 사용 |
| Electron 다운로드 | @electron/get ^5.1.0 → 5.1.0@electron-internal/extract-zip ^1.0.5 → 1.0.5 | Electron이 없을 때 받기·압축 해제 |
| ESLint / Prettier | ^10.9.0 → 10.9.0^3.9.6 → 3.9.6 | 정적 검사와 서식 확인 |
| 테스트 | node:testplaywright 1.63.0 | TypeScript 단위 테스트와 별도 UI 테스트 |
| 영상 처리 | Python, NumPy, SciPy, FFmpeg | 버전 잠금용 Python 의존성 파일은 없음. 일부 스크립트는 ffmpeg-9.0.1-essentials_build 경로를 지정 |
Cargo.toml, go.mod, pyproject.toml, Dockerfile은 없습니다.
핵심 런타임은 Node·Electron이며, Python·Swift는 주로 소재 제작을 돕습니다. Swift 인코더는 Apple의 AVFoundation·VideoToolbox를 사용합니다.
dsh-pet/tsdown.config.mjs에는 브라우저 client, 호스트 index, 독립 CLI standalone,
앱 app의 네 진입점이 있습니다. 파일 머리 주석에는 세 구성이라고 남아 있지만 실제 배열은 네 개입니다.
브라우저·호스트는 es2020, 독립 CLI·앱은 es2022를 목표로 합니다.
화살표는 데이터와 호출의 방향을 요약합니다. DSH 플러그인과 독립 CLI는 호스트와 Helper가 나뉘지만, 독립 앱은 호스트 로직과 창 관리 코드를 같은 Electron 메인 프로세스에 둡니다. 앱의 메인 프로세스를 합쳤다는 뜻이지 Electron의 렌더러 프로세스까지 하나라는 뜻은 아닙니다. 일반적인 메인·렌더러 구분은 Electron 공식 프로세스 모델에서도 확인할 수 있습니다.
dsh-pet/src/host/index.ts의 apply(ctx)는 호스트 서비스를 받아 라우트와 부수 효과를 등록합니다.
내부 handlePetRoute()가 기능별 요청을 처리합니다. 독립 실행은 이 로직을 복제하는 대신
dsh-pet/src/standalone/context.ts의 createStandaloneContext()로 필요한 모양의 ctx를 만듭니다.
대체 webServer.register()는 같은 핸들러를 배열에 모읍니다.
dsh-pet/src/standalone/server.ts가 그 배열을 Node HTTP 서버에 연결합니다.
effect()는 등록 함수가 돌려준 해제 함수를 모으고, dispose()는 역순으로 호출합니다.
창·타이머·라우트를 만드는 일에 종료 경로까지 짝지어 둔 것입니다.
가전제품의 내부 회로를 나라별로 다시 만드는 대신 플러그 어댑터를 바꾸는 방식입니다. 여기서 바뀌는 것은 서비스 공급자이며, 라우트와 캐릭터 동작 규칙은 같은 것을 씁니다.
| 진입점 | 환경이 제공하는 것 | 차이 |
|---|---|---|
dsh-pet/src/host/index.ts | DSH의 모델·자격 증명·세션·웹 서버 | DSH 플러그인으로 등록 |
dsh-pet/src/standalone/cli.ts | 대체 컨텍스트·로컬 서버·선택적 AI 서비스 | 기존 Helper를 별도 실행 |
dsh-pet/src/app/main.ts | 대체 컨텍스트·앱 라우터·선택적 AI 서비스 | DSH_PET_IN_PROCESS=1로 Helper 중복 실행을 건너뛰고 창 코드를 재사용 |
빌드도 이를 지원합니다. dsh-pet/tsdown.config.mjs는 독립 실행에서만 DSH 전용 패키지 세 개를
dsh-pet/src/standalone/shims/로 바꿉니다. noExternal로 먼저 번들 대상에 포함한 뒤 alias를 적용합니다.
외부 패키지로 그대로 남으면 대체 구현을 고를 기회조차 없기 때문입니다.
dsh-pet/src/host/config.ts의 readAllConfig()는 내장 기본값, 사용자 주 설정, 종류별 펫 파일을 읽습니다.
mergeEntry()는 최상위 필드를 검사해 유효한 값을 고르고,
mergePet()는 인스턴스의 숫자·불리언·표시 위치를 정규화합니다.
physics·font 같은 객체는 임의의 깊은 병합이 아니라 필드 단위의 전체 교체입니다.
잘못된 객체를 명시하면 해당 필드가 기본값으로 돌아갑니다. 반면 position은 하위 필드별로 병합합니다.
모든 중첩 객체가 같은 병합 규칙을 따를 것이라고 생각하면 설정이 예상과 달라집니다.
종류별 설정에는 packBase()가 허용한 공통 설정만 사용자층에서 전파합니다.
인스턴스의 whisperPrompt는 의도적으로 유효한 기본 문구를 미리 채우지 않습니다.
값을 비워 두는 것이 “종류의 설정을 계속 따라간다”는 뜻이므로, 저장 과정에서 상속이 고정값으로 변하지 않게 합니다.
dsh-pet/src/host/state.ts의 PollStateStore는 공통 sections와 펫별 pets를 관리합니다.
각 말단 항목에는 { counter, data }가 있고, 쓰기는 writeSection() 또는 writePet()를 거칩니다.
내부 next()는 다음 식으로 같은 밀리초의 연속 쓰기도 구별합니다.
// dsh-pet/src/host/state.ts의 next() 핵심
this.last = Math.max(Date.now(), this.last + 1);
return this.last;
// 응답 형태를 줄인 학습용 예시
{
counter: 1002,
sections: { workStatus: { counter: 1001, data: {} } },
pets: { main: { say: { counter: 1002, data: { text: "안녕" } } } }
}
클라이언트의 dsh-pet/src/shared/state.ts는 첫 응답의 버전을 flattenCounters()로 기록하고,
이후 takeChanged()로 달라진 말단 항목만 전달합니다.
dsh-pet/src/client/pet.ts의 PetMulti는 since와 timeout을 보내 장기 폴링합니다.
현재 클라이언트 대기는 1,000ms, 호스트가 허용하는 상한은 10,000ms입니다.
요청을 받아들였다는 사실과 화면에서 실제 동작이 끝났다는 사실은 별도로 기록해야 합니다.
dsh-pet/src/host/playback.ts의 PlaybackStore가 이 차이를 담당합니다.
POST /anim을 수락하면 호스트가 accepted를 기록하고, 렌더러는 실제 재생에 따라 playing,
ended / failed / cancelled를 보고합니다. 재생 시작 전에도 로딩 실패로 failed가 될 수 있고,
새 재생 요청이 기존 요청을 대체하면 호스트가 기존 요청을 cancelled로 바꿉니다.
끝난 상태는 늦게 도착한 보고로 다시 바뀌지 않습니다.
이 상태 저장소는 이벤트 전체를 보존하는 큐가 아닙니다. 같은 말단 항목을 빠르게 두 번 쓰면 중간 값은 조회 전에 덮일 수 있습니다.
첫 응답에서는 과거 말풍선을 재생하지 않지만, 메뉴처럼 현재 있어야 할 데이터는 별도로 읽어 넣습니다.
counter는 프로세스 안에서 증가를 보장할 뿐, 영구 저장 로그나 시스템 시계 변경까지 해결하는 분산 시계는 아닙니다.
dsh-pet/src/shared/physics.ts의 springStep()·throwStep(),
dsh-pet/src/shared/pickers.ts의 rollKind(), dsh-pet/src/shared/motion.ts의 planMove()는
DOM 없이 계산할 수 있습니다. 브라우저는 이를 TypeScript 번들로 쓰고,
dsh-pet/scripts/build-desktop-core.mjs는 같은 진입점 dsh-pet/src/shared/index.ts를 IIFE로 묶어 window.PetShared로 제공합니다.
반면 화면 그리기는 다릅니다. 브라우저의 makePetUI()는 React 훅을 받고,
데스크톱의 dsh-pet/runtime/electron-helper/sprite.js는 영상 DOM과 창 이동을 다룹니다.
공유 폴더 전체가 순수 함수인 것은 아닙니다. dsh-pet/src/shared/menu.ts에는 두 환경이 함께 쓰는 DOM 메뉴 구현도 있습니다.
이 분리는 테스트에도 유리합니다. 중력과 반사 계산을 확인하려고 Electron 창을 띄울 필요가 없습니다. 계산 결과를 검증하는 테스트와 실제 창이 올바르게 움직이는 UI 검사는 서로 다른 문제로 남깁니다.
저장소 루트의 README.md·scripts/는 전체 프로젝트를 설명하고,
실제 npm 패키지와 앱 소스는 dsh-pet/ 아래에 모여 있습니다.
파일을 찾을 때는 실행 환경을 먼저 고르고, 그다음 공유 로직으로 내려가면 길을 잃기 어렵습니다.
| 알고 싶은 것 | 읽는 순서 |
|---|---|
| 다음 동작 고르기 | dsh-pet/assets/config.jsonc → dsh-pet/src/shared/pickers.ts → dsh-pet/src/client/pet.ts |
| 캐릭터 던지기 | dsh-pet/src/shared/physics.ts → dsh-pet/src/shared/physics.test.ts → dsh-pet/runtime/electron-helper/sprite.js |
| 독립 실행 재사용 | dsh-pet/src/standalone/cli.ts → dsh-pet/src/standalone/context.ts → dsh-pet/src/host/index.ts |
| 모델 요청의 경계 | dsh-pet/src/standalone/ai-config.ts → dsh-pet/src/standalone/ai-provider.ts → dsh-pet/src/standalone/ai-transport.ts |
| 제3자 메뉴 확장 | dsh-pet/src/host/menu.ts → dsh-pet/src/shared/menu.ts → dsh-pet/src/client/pet.ts |
다음은 커밋 48007b6에서 파일 확장자별로 센 물리적 줄 수입니다.
빈 줄과 주석, 테스트를 포함하고 .git은 제외했습니다. 코드의 복잡도나 테스트 커버리지를 뜻하는 수치는 아닙니다.
| 언어·범위 | 파일 수 | 줄 수 |
|---|---|---|
TypeScript .ts | 149 | 34,418줄. 그중 .test.ts 55개, 13,658줄 |
| JavaScript 계열 | 35 | 7,284줄. .js / .mjs / .cjs 합계 |
| Python | 9 | 1,219줄 |
| Swift / 셸 | 각 1 | 161줄 / 116줄 |
| HTML | 3 | 4,907줄. 도구 페이지의 내장 코드도 포함 |
TypeScript는 dsh-pet/src/host/ 17,431줄, dsh-pet/src/client/ 6,646줄,
dsh-pet/src/shared/ 5,745줄, dsh-pet/src/standalone/ 3,970줄, dsh-pet/src/app/ 626줄입니다.
특히 호스트 쪽 테스트가 8,464줄이라, 실제 사용 조건과 실패 사례를 찾는 데 도움이 됩니다.
dsh-pet/src/**/*.test.ts를 설명서처럼 읽어 보세요. 예를 들어 dsh-pet/src/host/config.test.ts는 유효한 설정과 잘못된 설정을 만들고
실제 readAllConfig()의 결과를 비교합니다. 비공개 검증 함수를 그대로 시험하는 대신 외부에서 관찰할 결과를 고정합니다.
dsh-pet/src/shared/state.ts의 SayCue는 말하기와 지우기를 서로 다른 형태로 표현합니다.
빈 문자열을 특별한 명령으로 쓰면 일반 입력 검증과 충돌할 수 있습니다.
kind 같은 구분 필드를 두면 가능한 행동을 코드에서 드러낼 수 있습니다.
// 실제 SayCue에서 선택 필드를 생략한 학습용 축약
type SayCue =
| { kind: 'say'; text: string; duration?: number }
| { kind: 'clear' };
function describe(cue: SayCue): string {
if (cue.kind === 'clear') return '말풍선 지우기';
return cue.text; // 이 분기에서는 text가 있는 형태로 좁혀짐
}
배울 것: 선택 필드가 많은 객체 하나와, 구분 가능한 여러 형태의 합집합은 다릅니다. 작은 실습: 말풍선의 “숨김”과 “삭제”를 구별해야 한다면 필요한 형태를 먼저 종이에 써 보세요. 개념은 TypeScript 공식 타입 좁히기 문서와 연결됩니다.
dsh-pet/src/client/pet.ts의 switchTo()는 두 영상 요소를 번갈아 사용합니다.
뒤쪽 영상에 새 파일을 준비하고, loadeddata를 받은 뒤 앞쪽으로 바꿉니다.
이전 영상의 onended를 지우고 멈춰, 이미 교체된 영상의 종료 이벤트가 다음 행동을 건드리지 못하게 합니다.
여기에는 네 가지 수명 관리가 있습니다. AbortController로 파일 요청에 10초 제한을 걸고,
pendingRef.current?.gen으로 뒤늦은 결과를 버리고,
mountedRef로 이미 사라진 화면을 건드리지 않으며,
교체한 객체 URL은 URL.revokeObjectURL()로 해제합니다.
배울 것: “불러왔다”·“디코딩할 수 있다”·“재생이 시작됐다”는 서로 다른 시점입니다. 작은 실습: A 요청 뒤 B 요청을 보냈는데 B가 먼저 도착하는 시간표를 그리세요. 세대 번호가 없을 때 어떤 영상이 마지막에 보이는지 설명하면 비동기 경합을 이해한 것입니다.
dsh-pet/src/shared/physics.ts에서 기본 중력은 1400 px/s², 반발 계수는 0.78,
바닥 마찰은 2.5 /s입니다. throwStep()는 입력 시간 간격을 최대 0.05초로 제한합니다.
탭이 멈췄다가 돌아왔을 때 한 번에 멀리 순간 이동하는 상황을 줄이는 장치입니다.
// throwStep()의 계산 순서에서 경계 처리를 제외한 부분
const dt = Math.min(Math.max(dtRaw, 0), MAX_STEP_DT);
vy += physics.gravity * dt;
x += vx * dt;
y += vy * dt;
// 오른쪽 벽 충돌: 왼쪽으로 돌아서며 속력 감소
vx = -Math.abs(vx) * physics.restitution;
화면에서는 아래쪽으로 갈수록 y가 커집니다. 정지 상태에서 1/60초를 진행하면 중력으로
vy가 약 23.33 px/s 증가합니다. 벽에 1000 px/s로 부딪히면 반대 방향 속력은 780 px/s가 됩니다.
드래그의 springStep()는 SPRING_K=200, SPRING_C=30으로
목표 위치와 현재 위치의 차이, 현재 속도를 함께 사용합니다.
작은 실습: 기본 강도에서 속도 0, 위치 0, 목표 100, 시간 1/60초라면 새 속도는 약 333.33이고,
호출자가 위치를 갱신한 뒤에는 약 5.56이 됩니다. 단위를 붙여 손으로 계산해 보세요.
0.78은 속력의 78%를 뜻하며 에너지의 78%를 뜻하지는 않습니다.
물리 공식은 화면의 시간 단위가 맞아야 같은 느낌으로 동작합니다.dsh-pet/runtime/electron-helper/main.js는 펫마다 작은 BrowserWindow를 만들고,
transparent: true, frame: false, alwaysOnTop: true를 지정합니다.
화면 전체를 덮는 투명 창 하나를 만드는 방식이 아닙니다. 창은 캐릭터와 말풍선을 담도록 이동합니다.
픽셀이 투명하다고 클릭까지 자동으로 뒤에 전달되지는 않습니다.
메인 프로세스의 setIgnoreMouseEvents()와 preload의 setInteractive()·setInputBusy()가 이를 조정합니다.
드래그 중에 갑자기 클릭 통과로 바뀌면 pointerup을 놓칠 수 있으므로 입력 중 상태를 따로 전달합니다.
창 설정에는 contextIsolation: true, nodeIntegration: false가 있지만
sandbox: false도 있습니다. 이 세 설정은 같은 뜻이 아닙니다.
dsh-pet/runtime/electron-helper/preload.js는 contextBridge.exposeInMainWorld()로 정해진 동작을 노출합니다.
옵션의 의미는 공식 BrowserWindow 문서를 함께 보세요.
작은 실습: 창 제어, 영상 재생, 파일 읽기 각각이 어느 프로세스의 책임인지 표를 만들어 보세요.
dsh-pet/src/app/desktop-auth.ts의 installDesktopAuth()가 대상 origin과 로컬 최상위 렌더러를 확인한 뒤에만 헤더를 붙이는 이유도 설명해 보세요.
독립 AI의 DEFAULT_AI_SETTINGS는 enabled: false, automaticWhispers: false입니다.
최대 출력은 기본 1024 토큰, 요청 제한 시간은 30,000ms입니다.
createStandaloneAi()는 설정·모델·주소·키가 갖춰졌을 때만 준비 상태가 됩니다.
dsh-pet/src/host/whisper-policy.ts의 automaticWhisperSchedule()는 독립 실행에서
펫별 허용, AI 준비 상태, 자동 발화 허용을 모두 확인합니다.
dsh-pet/src/standalone/ai-provider.ts에는 동시 요청 제한, 분당 6회 제한, 취소 신호, 입력·출력 길이 제한도 있습니다.
작은 AI 기능에서도 사용자가 켠 기능과 실제 외부 요청이 시작되는 조건을 명확히 연결해야 합니다.
dsh-pet/src/standalone/ai-storage.ts의 writePrivateJson()은 임시 파일에 제한된 권한을 먼저 적용한 뒤 내용을 쓰고,
fsyncSync()와 renameSync()로 교체합니다. POSIX에서는 0600, Windows에서는 사용자 SID 기반 권한을 적용합니다.
키는 로컬 JSON 파일에 저장되며, 운영체제 비밀 저장소로 암호화하는 구현은 아닙니다.
작은 실습: dsh-pet/src/standalone/ai-config.ts의 설정 반환에서 키 대신 hasKey만 내보내는 경로를 추적하세요.
대화 기록은 $DSH_HOME/dsh-pet/memory.json에 보존하면서, 모델에 보내는 문맥은 sliceMemoryRounds()로 최근 몇 턴만 고르는 차이도 확인하세요.
기본 chatMemoryRounds는 5이며, 문맥을 줄인다고 저장된 과거 기록이 삭제되는 것은 아닙니다.
scripts/chroma_step02.py의 실제 변환은 NumPy 배열로 HSV 색상을 판정합니다.
scripts/normalize_step03.py는 처음과 끝의 서 있는 프레임을 읽어 중심·높이를 측정하고,
전체 동작의 범위도 확인합니다. 소재마다 크기가 달라 보이거나 발 위치가 튀는 문제를 제작 단계에서 줄이는 접근입니다.
scripts/encode_thumbs.py는 디코딩에 libvpx-vp9를 명시하고
format=yuva420p로 투명 채널을 유지합니다. 실행 화면의 dsh-pet/src/shared/constants.ts에는
CANVAS_H=360, FEET_Y=330, 고정된 HIT_BOX가 있습니다.
소재 형식을 바꿀 때는 재생 파일만 아니라 이 좌표 계약도 맞춰야 합니다.
scripts/encode_thumbs.py의 머리 설명은 예전의 360×360·1200×1200·dsh-pet/assets/thumb를 말하지만,
실제 상수는 640×360이고 현재 패키지는 dsh-pet/assets/webm을 배포합니다.
일부 스크립트의 “추가 의존성 없음”이라는 취지의 주석도 NumPy·SciPy import와 맞지 않습니다.
오래된 주석을 그대로 설치 조건으로 받아들이지 마세요.
dsh-pet/package.json의 test는 Node 내장 테스트 러너와 타입 제거 옵션을 사용하고,
test:ui는 별도의 dsh-pet/scripts/tests/*.test.mjs를 실행하도록 정의합니다.
테스트 API와 타이머 모의 기능의 개념은 Node 공식 테스트 문서에서 이어서 읽을 수 있습니다.
.github/workflows/release-app.yml은 Windows runner에서 Node 24·pnpm 12를 쓰고,
--frozen-lockfile 설치, 버전 일치 검사, 번들, 타입 검사, 패키징을 진행합니다.
기존 릴리스에 ZIP을 올리는 구성입니다. 이 workflow에는 단위 테스트·UI 테스트 실행 단계가 보이지 않습니다.
작은 실습: dsh-pet/scripts/prepare.js의 전체 빌드와 릴리스 workflow를 대조해 보세요.
전체 빌드에는 build:desktop-core가 있지만, 이 커밋의 앱 workflow는 bundle을 호출합니다.
dsh-pet/runtime/electron-helper/shared-core.js는 Git에서 제외되고 dsh-pet/scripts/dist.mjs도 이를 만들지 않으므로,
깨끗한 체크아웃에서 배포 자산의 생성 경로가 완결되는지 검토할 지점입니다. 실제 릴리스 ZIP의 포함 여부는 별도 확인이 필요합니다.
| 목적 | 필요한 환경 | 근거·주의할 범위 |
|---|---|---|
| DSH 플러그인 | DSH와 호환되는 Node 환경·웹 화면 | README의 개발·시험 버전은 DSH 0.2.1-rc.2. 호환 선언에는 0.2.0-rc.1 / rc.2도 포함 |
| 독립 CLI | Node >=22.12.0, Electron, 그래픽 세션 | engines.node와 dsh-pet/src/host/helper-process.ts. 기본 Electron 43.3.0 |
| 독립 앱 | Windows x64용 ZIP 배포 경로 | .github/workflows/release-app.yml이 Windows x64 산출물을 생성. 세 OS용 앱 배포를 모두 제공하는 CI는 아님 |
| 브라우저 영상 | VP9-alpha를 표시할 수 있는 브라우저 | README는 Chrome·Edge·Firefox에서 투명 재생을 확인했다고 설명 |
| Safari 대응 | HEVC-alpha MOV 소재와 별도 배포 | 기본 npm 자산은 WebM. .github/workflows/hevc-alpha.yml은 macOS 14 runner 사용 |
| AI 대화 | 지원 프로토콜의 API 주소·모델·키 | 독립 설정에서 명시적으로 활성화. 단순 애니메이션에는 모델이나 GPU가 필수 아님 |
| 소재 제작 | Python·NumPy·필요 시 SciPy·FFmpeg | 일부 스크립트는 Windows .tools/ 경로를 고정. HEVC 인코더는 macOS의 Swift 환경 사용 |
README는 Windows·Linux·macOS에서의 런타임 지원을 설명합니다. 코드도 운영체제별 Electron 경로를 고르지만, 이것만으로 모든 창 관리자와 하드웨어에서 같은 동작을 보장할 수는 없습니다. 특히 Electron의 플랫폼 안내는 Wayland에서 창 위치·크기 변경 등에 제약이 있다고 설명합니다.
hasGraphicalDisplay()는 Linux에서 DISPLAY 또는 WAYLAND_DISPLAY를 검사합니다.
둘 다 없으면 데스크톱 실행을 건너뛰며, DSH_PET_DESKTOP_FORCE=1은 예외 처리용입니다.
그래픽 세션 없는 서버가 캐릭터를 직접 그려 주는 것은 아닙니다.
공식 최소 CPU·RAM·GPU 사양은 명시돼 있지 않습니다. 재생용 WebM의 약 51.83 MiB는 소재 크기이며, Electron 런타임과 실행 중 메모리까지 포함한 크기가 아닙니다. 데스크톱에서 여러 펫을 띄우면 창·영상 디코더·상태 조회도 늘어납니다. 최소 사양을 임의의 숫자로 단정하기보다 인스턴스 수별로 측정해야 합니다.
캐릭터 한 개, 기본 크기, 자동 AI 호출을 끈 조건부터 비교하면 원인을 구분하기 쉽습니다. 이후 펫 수·영상 크기·자동 발화 주기를 하나씩 바꾸며 CPU, 메모리, 프레임 끊김을 기록하세요. 이는 측정 설계 제안이며 이 저장소의 성능 측정 결과는 아닙니다.
아래 과제는 종이 추적이나 별도 연습 파일로 시작할 수 있습니다. 외부 모델·잔액 서비스에 연결하지 않아도 핵심 원리를 확인할 수 있습니다. 과제의 완료 기준을 눈에 보이는 결과와 실패 사례로 정하면, 단순히 코드를 따라 쓰는 데서 벗어날 수 있습니다.
읽을 곳: dsh-pet/src/shared/pickers.ts의 rollKind()와 dsh-pet/assets/config.jsonc의 animationWeights.
할 일: 기본 가중치 { idle: 10, turn: 5, move: 5 }에
0.05 / 0.12 / 0.18 / 0.90을 넣은 결과를 적으세요. 같은 값을 fixed: true에서도 비교하세요.
제출물: 입력·일반 모드·고정 모드의 세 열로 된 표.
완료 기준: 일반 모드에서는 차례로 idle / turn / move / action, 고정 모드에서는
idle / action / action / action이 나와야 합니다. 고정 모드에서 빠진 확률이 어디로 가는지도 설명하세요.
// 경계값까지 추가하면 더 좋은 연습이 된다
const weights = { idle: 10, turn: 5, move: 5 };
const rolls = [0.0999, 0.10, 0.1499, 0.15, 0.1999, 0.20];
// 비교 연산이 미만인지 이하인지 원문에서 확인한다.
읽을 곳: dsh-pet/src/host/config.ts의 mergeEntry()·mergePet()와 dsh-pet/src/host/config.test.ts.
할 일: 기본 설정에 사용자층이 position.marginX만 바꾼 경우,
physics에 gravity만 쓴 경우, 두 인스턴스의 id가 같은 경우를 비교하세요.
검증 결과와 기본값으로 돌아가는 범위를 예상한 뒤 실제 분기와 대조합니다.
제출물: 세 입력의 최종 객체와 경고 이유.
완료 기준: 위치의 하위 필드 병합과 물리 객체의 전체 검증을 구별하고,
중복 id가 캐릭터 수에 미치는 영향을 설명해야 합니다.
읽을 곳: dsh-pet/src/client/pet.ts의 switchTo().
할 일: 파일 요청 대신 직접 완료시킬 수 있는 가짜 비동기 함수를 사용합니다. A를 먼저 요청하고 B를 나중에 요청한 뒤, B→A 순서로 완료하세요. 요청 번호를 비교하는 구현과 비교하지 않는 구현의 마지막 화면 상태를 적습니다.
제출물: 요청·완료·채택·폐기의 시간표.
완료 기준: 마지막 의도인 B를 유지해야 하며, 화면이 사라진 뒤 완료된 요청도 채택하지 않아야 합니다.
새 영상의 play()가 실패했을 때 회신할 상태까지 정해 보세요.
// 저장소 구현을 읽기 위한 독립적인 학습용 의사 코드
let generation = 0;
async function change(load: () => Promise<string>) {
const mine = ++generation;
const result = await load();
if (mine !== generation) return;
show(result);
}
// show와 load는 연습용 가짜 함수로 정의한다.
읽을 곳: dsh-pet/src/host/state.ts, dsh-pet/src/shared/state.ts, dsh-pet/src/host/playback.ts와 각 테스트.
할 일: 같은 밀리초에 말풍선을 두 번 쓰는 경우,
재생이 cancelled된 뒤 늦게 ended가 보고되는 경우,
대기 중인 연결이 취소되는 경우의 순서를 각각 그리세요.
제출물: 버전·현재 값·재생 상태·남은 대기자 수의 변화 표. 완료 기준: 같은 말단 항목의 중간 값이 보존되지 않을 수 있음을 설명하고, 종료된 재생 기록이 바뀌지 않으며 대기자와 타이머가 남지 않는 조건을 제시해야 합니다.
읽을 곳: dsh-pet/tsdown.config.mjs, dsh-pet/scripts/prepare.js,
dsh-pet/scripts/dist.mjs, dsh-pet/electron-builder.yml, .github/workflows/release-app.yml.
할 일: dsh-pet/lib/app.js, dsh-pet/runtime/electron-helper/shared-core.js, 타입 선언, WebM마다
생성 명령과 포함 규칙을 연결하세요. 생성 단계가 보이지 않는 자산에는 필요한 단계를 제안합니다.
제출물: “원본 → 생성 단계 → 파일 → 패키징 규칙” 표와 누락 검증 목록. 완료 기준: 미리 만들어진 로컬 파일이 없어도 모든 필수 자산에 생성 경로가 있어야 하며, 타입 검사 통과와 실행 자산 완전성을 별도로 확인해야 합니다.
함수와 객체를 읽을 수 있다면 시작할 수 있습니다. async/await가 낯설다면 2주 차에 시간을 더 쓰세요.
화면을 먼저 꾸미기보다 계산·상태·입력 경계를 차례로 익히면, 환경이 달라져도 배운 설계를 재사용할 수 있습니다.
| 주차 | 학습 주제·읽을 곳 | 남길 결과물 |
|---|---|---|
| 1주 | TypeScript 객체·합집합·타입 좁히기dsh-pet/src/shared/types.ts, dsh-pet/src/shared/pickers.ts | 행동 선택 표와 잘못된 입력 예시 5개. 타입과 실행 시 검사의 차이 설명 |
| 2주 | Promise·비동기 반복·취소·React 수명dsh-pet/src/client/pet.ts, dsh-pet/src/standalone/context.ts | 응답 순서가 뒤집힌 시간표와 정리해야 할 자원 목록 |
| 3주 | 입력 검증·설정 계층·파일 저장dsh-pet/src/host/config.ts, dsh-pet/src/standalone/ai-storage.ts | 부분 병합과 전체 교체 비교표, 저장 도중 실패할 때의 결과 |
| 4주 | 상태 버전·장기 폴링·상태 기계dsh-pet/src/host/state.ts, dsh-pet/src/host/playback.ts, dsh-pet/src/host/lock.ts | 성공·실패·취소·만료를 포함한 순서도와 검증 사례 |
| 5주 | Electron 메인·렌더러·preload·좌표계dsh-pet/runtime/electron-helper/, dsh-pet/src/shared/physics.ts | 프로세스 책임 표, 화면 좌표와 속도 단위가 적힌 계산 노트 |
| 6주 | 번들·자산·재현 가능한 배포dsh-pet/tsdown.config.mjs, dsh-pet/scripts/prepare.js, 두 workflow | 생성물의 의존 관계 지도와 앱·소재 배포의 차이 설명 |
HelperProcess의 종료와 재시작을 읽습니다.
restartBackoffDelayMs()는 기본 750ms에서 지수적으로 늘려 최대 30초로 제한하고, 연속 실패 기본 12회에서 자동 재시작을 멈춥니다.자동차를 배우며 처음부터 도색과 엔진과 도로망을 한꺼번에 보지 않는 것과 같습니다. 작은 함수로 “바퀴가 어떻게 도는지”를 알고, 상태로 “언제 움직이는지”를 배우고, 마지막에 실행 환경과 배포로 “다른 사람에게 어떻게 전달하는지”를 연결하세요.
| 용어 | 쉬운 설명 |
|---|---|
| DSH | DeepSeek Harness. 이 펫이 모델·세션·설정 화면 서비스를 받아 쓰는 호스트 |
| Cordis | 서비스를 등록하고 필요한 서비스가 준비되면 플러그인을 연결하는 기반 |
| 의존성 주입 | 함수가 서비스를 직접 만들지 않고 바깥에서 받는 것. apply(ctx)가 예 |
| 어댑터 | 다른 환경을 같은 인터페이스로 맞추는 연결부. 독립 실행의 대체 컨텍스트 |
| 렌더러 | 영상·말풍선·메뉴를 실제 화면에 그리는 부분 |
| preload | Electron 화면 코드가 시작되기 전에 필요한 연결 기능을 준비하는 코드 |
| IPC | 프로세스 간 통신. 창 이동 요청 등을 렌더러에서 메인으로 전달 |
| 클릭 통과 | 위에 있는 투명 창이 입력을 받지 않고 아래 앱으로 보내는 동작 |
| DIP / DPI | 화면 배율과 관련된 논리 좌표·픽셀 밀도. 다중 모니터에서는 좌표 단위를 맞춰야 함 |
| 순수 함수 | 같은 입력에 같은 결과를 주며 외부 상태를 바꾸지 않는 계산. rollKind()가 예 |
| 타입 좁히기 | 조건을 확인한 뒤 가능한 타입을 줄여 안전하게 필드를 읽는 것 |
| 검증 경계 | 외부 입력이 내부 로직에 들어가기 전에 형태와 허용값을 확인하는 입구 |
| JSONC | 주석을 허용하는 JSON 계열 설정 형식. 이 저장소는 전용 제거 로직으로 주석을 처리 |
| 장기 폴링 | 변화가 없으면 응답을 잠시 기다리고, 변화나 제한 시간이 오면 돌려주는 조회 |
| counter | 상태가 바뀌었는지 비교하는 번호. 현재 값 자체와 분리돼 있음 |
| 상태 기계 | 허용된 상태와 이동 규칙의 집합. 재생의 수락·시작·종료·실패·취소 |
| 멱등성 | 같은 요청이나 보고를 반복해도 결과가 불필요하게 누적되지 않는 성질 |
| TTL | 정해진 유효 시간. 펫 제어 잠금은 expiresAt 이후 영원히 남지 않게 설계 |
| 더블 버퍼 | 현재 영상을 보여 주는 동안 다른 영상 요소에 다음 장면을 준비하는 방식 |
| alpha 채널 | 픽셀의 투명도 정보. 영상 재생 가능 여부와 투명도 보존 여부는 별개 |
| VP9-alpha | 투명도를 담은 VP9 영상. 기본 배포의 WebM 자산에 사용 |
| HEVC-alpha | Apple 계열 재생 경로를 위해 별도로 만드는 투명 HEVC 영상 |
| 크로마키 | 특정 배경색을 찾아 투명하게 만드는 처리. 여기서는 HSV 기반 녹색 판정 |
| 락 파일 | 패키지의 실제 해결 버전을 기록하는 파일. 제어 권한을 뜻하는 잠금과는 다른 용어 |
| IIFE | 정의하자마자 실행하는 함수. 브라우저 번들의 전역 이름 충돌을 줄이는 껍데기 |
| shim | 없는 모듈의 필요한 인터페이스를 대신 제공하는 작은 구현 |
| ASAR | Electron 앱의 파일을 묶는 보관 형식. 코드 보안을 보장하는 암호화는 아님 |
| 지수 백오프 | 반복 실패할수록 재시도 간격을 늘려 실패가 연속 폭주하지 않게 하는 방식 |
이 용어들은 펫 앱에만 쓰이지 않습니다. 업로드 UI, 채팅 앱, 작업 상태 표시기, 작은 데스크톱 도구에서도 비동기 결과·설정·렌더링·종료 조건을 연결할 때 같은 개념을 사용합니다.
공식 문서는 최신 버전 설명으로 바뀔 수 있습니다. 소스의 동작은 커밋 48007b6,
패키지 버전은 이 커밋의 선언과 잠금 파일을 기준으로 읽으세요.
| 항목 | 값과 해석 |
|---|---|
| 선정 | GitHub Trending TypeScript Weekly #12 |
| 스타 / 포크 | 3,663 / 266 · 2026-10-23 GitHub 기준 |
| 생성일 | 2026-08-14 |
| 주 언어 | TypeScript |
| 최신 릴리스 | v0.3.8 · 2026-10-22 |
| 최근 push | 2026-10-23 |
| 최근 커밋 | 48007b6 · 2026-10-23제3자 메뉴의 표시 대상 지정 추가, 생략하면 양쪽 표시 |
| 코드 규모 | TypeScript 34,418줄 · JavaScript 계열 7,284줄 · Python 1,219줄 주석·빈 줄·테스트 포함, 커밋 기준 파일별 집계 |
| 내장 재생 소재 | WebM 106개 · 약 51.83 MiB. 소개용 GIF 106개는 약 64.28 MiB이며 npm 배포 목록에서 제외 |
| 라이선스 | MIT · LICENSE에 2026 PC2005-cloud 표기 |
| 호환성 선언 | DSH 0.2.0-rc.1, 0.2.0-rc.2, 0.2.1-rc.2는 compatible0.1.7-rc.2는 incompatible |
독립 AI: README와 일부 머리 주석은 독립 실행에서 대화가 불가능하다고 설명하지만,
이 커밋의 dsh-pet/src/standalone/cli.ts는 createStandaloneAi()를 주입합니다.
dsh-pet/src/standalone/options.ts의 도움말도 API 설정 후 활성화를 안내합니다. 미설정 상태의 실패와 기능 자체의 부재를 구별하세요.
API 범위: README의 “페이지 내부 서비스로 제어”라는 취지의 개요보다 현재 코드의 범위가 좁습니다.
dsh-pet/src/client/app.ts가 등록하는 petApi7340는 pose·progress 읽기 서비스입니다.
제어 요청은 HTTP API로 나뉘며, 독립 실행 HTTP 경계는 Host·Origin·토큰을 검사합니다.
README의 “인증 없음”을 모든 실행 형태에 적용하지 마세요.
재생·배포: Safari용 MOV는 기본 npm 파일 목록에 없습니다. 소스에는 WebKit에서 MOV를 고르는 경로와 누락 시 WebM으로 되돌리는 경로가 있지만, 되돌리기가 투명도까지 보장하지는 않습니다. 앱 릴리스 workflow의 공유 코어 생성 단계도 실제 배포 자산과 함께 점검해야 합니다.
자료의 범위: 이 글은 해당 커밋의 정적 코드에 근거합니다. OS별 실제 동작, 성능, 릴리스 ZIP의 완전성을 보장하는 실행 검증 자료는 아닙니다. 자동 대화는 외부 API 비용과 문맥 전송을 수반할 수 있고, 대화 기록·독립 AI 키는 로컬 파일로 남습니다. 녹색 배경 영상과 수동 보정까지 필요한 소재 제작을 완전 자동 생성으로 이해하지 마세요.