DAP Plugin Platform
DAP Plugin Docs
DAP 플러그인은 데스크톱 위의 DAP 경험에 명령, 액션, 설정, 트레이 패널, 단축키, 팔레트 UI와 로컬 서비스를 추가하는 확장 모듈입니다. 이 문서는 실제 플러그인을 만들고 배포하는 데 필요한 최소 경로와 API 레퍼런스를 제공합니다.
현재 Plugin Platform 버전은 0.3.0, manifest 기준은 manifest_version: 2, 전체 API 기준 최소 앱 버전은 DAP 1.4.1입니다. 0.3은 데일리 브리핑과 Host 관리 OAuth·커넥터 상태 API를 추가한 하위 호환 릴리스입니다.
Codex, Claude, ChatGPT에 전달할 문서 링크와 프롬프트를 바로 복사합니다.
Start 첫 플러그인 만들기5분 안에 액션 하나를 등록하고 DAP에서 호출합니다.
Reference ctx API 확인commands, actions, settings, host service를 빠르게 찾습니다.
Ship 설치와 배포로컬 설치 경로와 카탈로그 배포 형식을 확인합니다.
AI로 플러그인 만들기
AI 에이전트에게 긴 문서를 직접 설명하지 않아도 됩니다.
아래 프롬프트에 만들고 싶은 기능만 적어 전달하면, AI가 llms.txt에서 필요한 문서와 규칙을 따라가도록 설계했습니다.
Read https://project-undonghae.github.io/desk-ai-pet/llms.txt
and create a DAP plugin that [원하는 기능을 적어주세요].
Return:
- plugin.yaml
- a self-contained ESM entry file
- optional palette/tray static files
- required permissions and why they are needed
- local install steps
- known limitations
Follow manifest_version: 2 and Plugin Platform 0.3.0.
Do not invent Host APIs.
AI가 가장 먼저 읽을 공식 목차와 플러그인 작성 규칙입니다.
CONTEXT ai-context.md복붙하거나 로컬 에이전트에게 전달하기 쉬운 압축 컨텍스트입니다.
API ctx API reference액션, 명령, 설정, host service를 구현할 때 확인합니다.
VERSION Platform changelog플러그인 개발 계약의 버전별 추가·수정 내역을 확인합니다.
사람은 원하는 기능만 구체적으로 적고, AI에게는 llms.txt URL을 함께 전달하세요. 예: “선택한 한국어 문장을 공손하게 다듬는 DAP 플러그인을 만들어줘.”
Overview
DAP 플러그인은 plugin.yaml과 자기완결 ESM .mjs 진입 파일로 구성됩니다.
manifest에는 메타데이터와 권한만 선언하고, 기능 등록은 activate(ctx)에서 수행합니다.
현행 DAP 플러그인 문서는 Electron/TypeScript 기반 Host API를 기준으로 합니다. 예전 Python/PyQt 플러그인 문서는 historical 문서로만 취급합니다.
플러그인 진입 파일은 main process에서 실행되는 신뢰 기반 코드입니다. permissions[]는 사용자 동의와 Host API 노출 경계이지 프로세스 sandbox가 아닙니다. DAP은 공식 카탈로그의 공개 GitHub 저장소만 설치합니다.
Quickstart
1. 폴더 만들기
my-plugin/
plugin.yaml
dap_my_plugin/
plugin.mjs
README.md
2. manifest 작성
id: com.example.my_plugin
name: My Plugin
version: 1.0.0
manifest_version: 2
entry: dap_my_plugin.plugin:activate
description: DAP에서 실행되는 첫 플러그인
author: Your Name
min_app_version: "1.4.1"
surface: user
permissions: []
execution_modes:
- user
3. 액션 등록
// dap_my_plugin/plugin.mjs
export function activate(ctx) {
ctx.actions.registerAction({
id: "hello",
callback: () => {
ctx.host.bubble.speak("Hello from plugin");
return "Hello from plugin";
},
});
ctx.radialMenu.addItem({
itemId: "hello",
label: "Hello",
actionId: "hello",
priority: 50,
});
}
4. 로컬 설치
Windows:
%APPDATA%\dap\plugins\com.example.my_plugin\
macOS:
~/Library/Application Support/dap/plugins/com.example.my_plugin/
진입 파일은 bare import가 없는 자기완결 .mjs여야 합니다. 로컬 폴더를 설치 경로에 둔 뒤 DAP을 재시작하거나 설정의 플러그인 토글을 다시 켜서 로드합니다.
Core concepts
| 개념 | 설명 |
|---|---|
| Manifest | plugin.yaml. id, 이름, 버전, entry, 권한을 선언합니다. |
| Entry | module.path:activate. 플러그인의 ESM 진입점입니다. |
| Activation | DAP가 플러그인을 로드할 때 activate(ctx)를 호출합니다. |
| Registration | 액션, 메뉴, 단축키, 설정 섹션은 등록 핸들로 추적되고 비활성화 시 정리됩니다. |
| Host services | 클립보드, 저장소, 팔레트, 회의 캡처, 계정, 이미지 생성 같은 기능은 ctx.host.*로만 접근합니다. |
| Permissions | 권한이 필요한 host service는 permissions[]에 최소 범위로 선언합니다. 미선언 서비스는 undefined입니다. |
| Surface slots | surface_slots: [tray_panel]처럼 DAP가 제공하는 시각 표면을 manifest에 선언합니다. |
| AI context | context_contributors에 선언한 id만 ctx.aiContext.contribute()로 등록할 수 있습니다. |
Guides
명령 등록하기
채팅 또는 스팟라이트 입력을 자연어로 라우팅해야 할 때 ctx.commands를 사용합니다.
ctx.commands.addCommand({
id: "weather",
title: "날씨",
matchers: [
{ type: "keyword", patterns: ["날씨", "weather"], priority: 40 },
],
backend: { type: "builtin", handler: "dap.weather.current" },
});
설정 섹션 추가하기
ctx.settings.registerSettingsSection({
sectionId: "general",
title: "My Plugin",
spec: {
fields: [
{ key: "compact", label: "간단히 보기", type: "toggle", default: false },
{ key: "mode", label: "모드", type: "select", default: "fast",
options: [
{ value: "fast", label: "빠르게" },
{ value: "careful", label: "정교하게" },
] },
{ key: "note", label: "메모", type: "text" },
],
},
});
const values = ctx.host.settings.values("general");
ctx.host.settings.set("general", "compact", true);
트레이 허브 패널 등록하기
surface_slots: [tray_panel]과 window.palette 권한을 manifest에 선언한 뒤 등록합니다. 패널은 샌드박스 iframe이며 최대 64KB의 JSON형 메시지만 주고받습니다.
# plugin.yaml
permissions:
- window.palette
surface_slots:
- tray_panel
const panel = ctx.trayPanel.register({
id: "status",
page: "tray/index.html",
height: 210,
priority: 10,
});
panel.onMessage((message) => {
if (message?.type === "refresh") refresh();
});
panel.postMessage({ type: "status", items });
컴팩트 패널 권장 높이는 120–240px입니다. 상세 화면은 ctx.host.windows.openPalette()로 분리하고, 일반 트레이 액션은 ctx.trayMenu.addItem({ showInContextMenu: true })로 명시합니다.
팔레트 UI 열기
팔레트는 플러그인 전용 HTML UI입니다. 페이지는 권한 API를 직접 받지 않고 window.dapPalette 메시지 브리지만 사용합니다.
export function activate(ctx) {
let palette = null;
const open = () => {
if (palette && !palette.isDestroyed()) {
palette.toggle();
return;
}
palette = ctx.host.windows.openPalette({
page: "palette/index.html",
width: 360,
height: 520,
frame: false,
});
palette.onMessage((msg) => {
if (msg.type === "ready") {
palette.postMessage({ type: "items", items: ["A", "B"] });
}
});
palette.show();
};
ctx.actions.registerAction({ id: "openPalette", callback: open });
ctx.radialMenu.addItem({ itemId: "palette", label: "Palette", actionId: "openPalette" });
return () => palette?.close();
}
API reference
Manifest fields
| 필드 | 필수 | 설명 |
|---|---|---|
id | 예 | 플러그인 고유 id. 카탈로그 id와 같아야 합니다. |
name | 예 | 사용자에게 표시할 이름입니다. |
version | 예 | 플러그인 버전입니다. |
entry | 예 | module.path:callable 형식의 진입점입니다. |
manifest_version | 권장 | 현행 문서는 2를 기준으로 합니다. |
description / author | 아니요 | 사용자 설명과 작성자 메타데이터입니다. |
min_app_version | 아니요 | 필요한 최소 DAP 버전입니다. 충족하지 못하면 설치 전에 중단합니다. |
surface | 아니요 | user(기본) 또는 dev 빌드 전용 dev입니다. |
permissions | 아니요 | 사용할 권한 토큰 목록입니다. |
execution_modes | 아니요 | user, builtin, scheduled 등 실행 형태 메타데이터입니다. |
context_contributors | 아니요 | ctx.aiContext로 등록할 기여 id 목록입니다. |
surface_slots | 아니요 | tray_panel 또는 briefing.daily 기여를 사용하기 전에 선언합니다. |
추가 필드는 거절됩니다. manifest에는 명령·matcher·backend를 넣지 말고 activate(ctx)에서 등록하세요.
ctx namespaces
| API | 용도 |
|---|---|
ctx.actions.registerAction | 호출 가능한 액션을 등록합니다. |
ctx.commands.addCommand | 자연어/라벨 기반 명령을 등록합니다. |
ctx.settings.registerSettingsSection | 설정 UI 섹션을 등록합니다. |
ctx.trayMenu.addItem | 트레이 메뉴 항목을 추가합니다. |
ctx.radialMenu.addItem | 펫 래디얼 메뉴 항목을 추가합니다. |
ctx.shortcuts.registerShortcut | 재바인딩 가능한 전역 단축키를 등록합니다. |
ctx.trayPanel.register | 트레이 허브의 샌드박스 시각 패널을 등록합니다. |
ctx.aiContext.contribute | manifest에 선언한 짧은 AI 컨텍스트를 기여합니다. |
ctx.briefing.contribute | briefing.daily에 오늘의 짧은 사용자용 요약을 기여합니다. |
Host services
| 권한 | 서비스 | 설명 |
|---|---|---|
| 권한 불필요 | ctx.host.bubble | 펫 말풍선을 출력합니다. |
| 권한 불필요 | ctx.host.llm | 활성 LLM provider로 텍스트를 생성합니다. |
| 권한 불필요 | ctx.host.clipboard | 현재 클립보드 텍스트를 읽고 씁니다. manifest 토큰은 설치 고지용입니다. |
| 권한 불필요 | ctx.host.settings | 이 플러그인의 선언형 설정값을 읽고 씁니다. |
storage.private | ctx.host.storage | 플러그인 전용 격리 저장소입니다. |
window.palette | ctx.host.windows | 플러그인 팔레트 창을 엽니다. |
clipboard.history | ctx.host.clipboardHistory | 클립보드 기록을 읽습니다. 사용자 opt-in이 필요합니다. |
input.synthesize | ctx.host.paste | 이전 foreground 앱에 붙여넣습니다. |
presentation.overlay | ctx.host.presentation | 발표용 투명 오버레이를 열고 제어합니다. |
meeting.capture | ctx.host.meeting | 지원 여부 확인 후 회의 캡처·상태·전사 이벤트를 사용합니다. |
ai.accounts | ctx.host.aiAccounts | 로그인된 Claude/Codex 계정과 공개된 사용량 메타데이터를 조회합니다. |
image.generate | ctx.host.imageGen | Codex provider로 PNG를 생성해 bytes로 받습니다. |
oauth.connect | ctx.host.oauth | Host가 관리하는 OAuth 연결·상태·access token·해제 흐름을 사용합니다. |
connectors.read | ctx.host.connectors | 현재 Gmail 커넥터의 상태와 Host가 만든 사용자 안내를 읽습니다. |
Permissions
요청 동작에 꼭 필요한 토큰만 선언하세요. 설치 화면은 선언 권한을 고지하며, 미선언 권한 서비스는 플러그인 컨텍스트에 제공하지 않습니다.
| 토큰 | 용도 | 추가 조건 |
|---|---|---|
storage.private | JSON·blob 격리 저장소 | 플러그인별 기본 500MB cap |
window.palette | 팔레트와 tray_panel UI | 페이지는 플러그인 루트 안, 외부 네트워크 차단 |
clipboard.history | 클립보드 기록 | 민감·기본 OFF, 사용자가 설정에서 별도 opt-in |
input.synthesize | 이전 앱에 붙여넣기 | macOS Accessibility 권한 필요 |
dragdrop.export | 팔레트에서 이미지·파일 드래그 | 텍스트 드래그는 HTML5 native drag 사용 |
presentation.overlay | 발표 오버레이 | 투명·클릭스루 상태를 명시적으로 제어 |
meeting.capture | 마이크·시스템 오디오 전사 | OS 권한과 capability gate, 실행 중 표시 |
ai.accounts | 계정·구독 한도 메타데이터 | token/cookie/keychain 값은 전달되지 않음 |
image.generate | AI 이미지 생성 | Codex provider 전용, 사용자 quota 소모 |
oauth.connect | OAuth 서비스 연결 | 브라우저 흐름·refresh token 저장은 DAP이 관리 |
connectors.read | Gmail 커넥터 상태 | 읽기 전용 상태와 Host가 만든 사용자 안내만 제공 |
현재 entry module은 신뢰된 in-process 코드입니다. 권한 브로커는 Host API 노출을 제한하지만 악성 플러그인을 프로세스 수준에서 격리하지는 않습니다.
Examples
Daily briefing and OAuth
// plugin.yaml: surface_slots: [briefing.daily]
ctx.briefing.contribute({
id: "today",
provider: () => "오늘 일정 2개 · 마감 할 일 1개",
});
// plugin.yaml: permissions: [oauth.connect]
const status = await ctx.host.oauth.status("google-calendar");
if (!status.connected) {
await ctx.host.oauth.connect({
connectionId: "google-calendar",
provider: "google",
scopes: ["https://www.googleapis.com/auth/calendar.readonly"],
});
}
OAuth 브라우저 흐름과 refresh token 보관은 DAP Host가 맡습니다. 플러그인은 refresh token을 직접 받거나 저장하지 않습니다. connectors.read를 선언한 플러그인은 현재 ctx.host.connectors.status("gmail")로 { state, via, hint }를 받습니다. state는 connected, unavailable, unknown 중 하나이며, 판별 자체가 실패한 unknown에서는 기능을 차단하지 말고 명시적인 unavailable에서만 Host의 hint를 안내하세요.
Storage
await ctx.host.storage.setJson("pins", ["a", "b"]);
const pins = await ctx.host.storage.getJson("pins");
await ctx.host.storage.delete("pins");
LLM
const answer = await ctx.host.llm.generate(
"다음 문장을 더 자연스럽게 다듬어줘: " + text,
30
);
Meeting capability gate
const capability = ctx.host.meeting.capabilities();
if (!capability.available) {
ctx.host.bubble.speak(capability.reason ?? "회의 캡처를 사용할 수 없어요.");
return;
}
const offTranscript = ctx.host.meeting.onTranscript((event) => {
palette.postMessage({ type: "meeting.transcript", event });
});
await ctx.host.meeting.start({ source: "both", sourceLanguage: "en", targetLanguage: "ko" });
// cleanup에서 await ctx.host.meeting.stop(); offTranscript();
Image generation
const image = await ctx.host.imageGen.generate("고양이 밈");
const blobId = await ctx.host.storage.putBlob(image.bytes, {
mime: image.mime,
name: image.name,
});
Palette page
<!doctype html>
<meta charset="utf-8" />
<button id="close">close</button>
<ul id="list"></ul>
<script>
const list = document.getElementById("list");
window.dapPalette.onMessage((msg) => {
if (msg.type !== "items") return;
list.innerHTML = "";
for (const item of msg.items) {
const li = document.createElement("li");
li.textContent = item;
list.append(li);
}
});
window.dapPalette.postMessage({ type: "ready" });
document.getElementById("close").onclick = () => window.dapPalette.close();
</script>
Distribution
Catalog entry
플러그인은 공개 GitHub 저장소에 두고, Project-Undonghae/dap-plugins의 plugin_catalog.json에 PR을 보냅니다.
{
"plugins": [
{
"id": "com.example.my_plugin",
"name": "My Plugin",
"description": "한 줄 설명",
"repo": "https://github.com/example/dap-my-plugin.git",
"ref": "v1.0.0"
}
]
}
repo는owner/name또는 GitHub HTTPS URL을 사용합니다. 저장소는 public이어야 합니다.ref는 브랜치·태그·SHA이며 재현 가능한 설치를 위해 버전 태그를 권장합니다.plugin.yaml의id와 카탈로그id가 다르면 설치를 거절합니다.- DAP은 GitHub tree/raw API로 파일을 받아 설치합니다. 사용자 PC에서 git, zip 해제, npm install, 빌드를 실행하지 않습니다.
- 설치는 명시적인 신뢰 동작이며 새 플러그인은 즉시 활성화됩니다. 이전에 비활성화한 id는 그 상태를 유지합니다.
- 최대 200파일·20MB이며
node_modules,.git,.github는 제외됩니다. - 업데이트마다
plugin.yaml의version을 올리고 카탈로그ref를 새 태그로 갱신하세요. 권한이 추가되면 사용자가 다시 동의합니다.
Versioning
DAP 앱 릴리스 버전, Plugin Platform 버전, manifest 버전, 개별 플러그인 버전은 서로 다른 책임을 가집니다.
플러그인 개발자에게 영향을 주는 변경은 개발문서와 docs/plugin-platform 아래의 관리 파일을 함께 갱신합니다.
| 변경 | 필수 갱신 | 버전 처리 |
|---|---|---|
| 새 host API, permission, manifest 선택 필드 | API reference, changelog, version.json | Plugin Platform minor |
| API 설명 보강, 예시 수정, 비호환 없는 오류 수정 | 관련 문서, changelog | Plugin Platform patch |
| API 제거, 인자 변경, 권한 필수화, manifest 필수 필드 변경 | 문서 전체, migration note, changelog | Plugin Platform major 또는 manifest_version 증가 |
| 카탈로그 필드 추가/변경 | Distribution, plugins.html 등록 안내, fallback data | Plugin Platform minor 또는 major |
| 개별 플러그인 등록/업데이트 | 카탈로그 ref, 필요 시 plugins.html fallback | 개별 플러그인 버전 |
docs/plugin-platform/version.json은 Plugin Platform의 현재 버전과 호환성 기준입니다.docs/plugin-platform/changelog.md는 앱 릴리스 노트와 별도로 플러그인 개발자용 변경사항을 기록합니다.manifest_version은 manifest 해석 방식이 호환되지 않을 때만 올립니다.
Troubleshooting
| 증상 | 확인할 것 |
|---|---|
| 플러그인이 보이지 않음 | 설치 폴더명, plugin.yaml 위치, id 일치 여부를 확인합니다. |
| 활성화 실패 | entry가 실제 .mjs named export를 가리키는지 확인합니다. |
| host service가 undefined | 필요한 권한을 permissions[]에 선언했는지 확인합니다. |
| 팔레트가 열리지 않음 | window.palette 권한과 page 경로가 플러그인 루트 안인지 확인합니다. |
Changelog
2026-08-05: Plugin Platform 0.3.0. briefing.daily, ctx.briefing, oauth.connect/ctx.host.oauth, connectors.read/ctx.host.connectors를 추가했습니다. 최소 앱은 DAP 1.4.1이며 manifest v2는 그대로 호환됩니다.
2026-07-29: Plugin Platform 0.2.0. tray_panel, 동적 tray submenu, ctx.aiContext, 설정 쓰기, presentation/meeting/AI accounts/image generation Host API와 최신 설치·업데이트 계약을 반영했습니다. manifest v2는 그대로 호환됩니다.
2026-07-05: Plugin Platform 0.1.0 기준 문서 구조를 정리했습니다. Electron/TypeScript Host API, manifest v2, palette, distribution, versioning 구조를 반영했습니다.