플러그인 개발
플러그인 작성하기
플러그인은 AI WorkDeck에 새로운 도구를 추가하여 AI가 대화 중에 여러분의 코드를 호출할 수 있게 합니다. 이 가이드는 최소 동작 예제로 시작하여 리뷰 제출까지 다룹니다.
플러그인을 게시할 수 있는 사용자
무료 오픈소스 플러그인은 로그인한 모든 계정이 제출할 수 있으며, 별도의 인증이 필요하지 않습니다.
플러그인이나 Pack에 가격을 책정하거나 클로즈드소스 코드를 게시하려면 인증된 개발자 계정(신원 + 정산 수단, 수동 검토)이 필요합니다. 다음에서 신청할 수 있습니다:
개발자 센터SDK 1.6 views, connections, MCP, scoped settings, $schema/default compatibility fixes and host services require the new host. This implementation has not been released; installed clients do not gain support automatically. The simulator uses synthetic data and is not host compatibility evidence.
먼저 결정하세요: 플러그인이 필요한가요, Skill이 필요한가요
둘은 혼동하기 쉽고, 잘못 선택하면 많은 작업이 낭비됩니다. 차이점은 한 문장으로 요약할 수 있습니다: 코드가 필요하면 플러그인을, 명확하게 설명하는 것만으로 충분하다면 Skill을 작성하세요.
| 시나리오 | 사용 사례 |
|---|---|
| 외부 시스템의 API를 호출하여 데이터 가져오기 | 스킬 |
| 특수 파일 형식 파싱 | 스킬 |
| AI가 귀사의 형식으로 검토 의견을 작성하도록 하기 | 스킬 |
| 특정 유형의 사안에 대한 절차 및 산출물 구조 정의 | 스킬 |
스킬은 일반 텍스트(프롬프트와 트리거 단어)입니다. 제출 시 검토 없이 바로 게시되며 훨씬 빠르게 작성할 수 있습니다. 스킬로 필요를 충족할 수 있다면, 스킬 작성하러 가기 후 이 페이지의 나머지 부분은 건너뛰세요.
5분 만에 첫 플러그인 실행하기
플러그인은 JAR로 컴파일된 Java 프로젝트와 manifest.json을(를) ZIP으로 압축하여 제출한 것입니다. JDK 21과 Maven이 필요합니다.
위의 템플릿 프로젝트를 다운로드하세요. 그 자체로 완전히 작동하는 예제입니다. 중요한 파일은 두 개뿐입니다. 첫 번째는 도구 클래스입니다:
package com.example.myplugin;
import dev.langchain4j.agent.tool.Tool;
/**
* Plugin tool class.
*
* Three hard rules — break any one of them and the tool will not be visible to the AI:
* 1. Must have a no-arg constructor (instantiated via reflection by the host);
* 2. Annotate tool methods with @Tool; the method name is the tool name and must match manifest.json tools[].name;
* 3. Use String parameters and return values for simplicity (serialize complex structures as JSON strings).
*
* The @Tool description is read by the AI and determines when it invokes this tool.
* State clearly when to call it rather than only how it is implemented.
*/
public class MyTools {
@Tool("Count characters and CJK characters in a text passage and return a readable summary. Call when the user asks for character or word counts.")
public String countChinese(String text) {
if (text == null || text.isBlank()) {
return "Empty input: 0 characters";
}
long cjk = text.codePoints()
.filter(cp -> Character.UnicodeScript.of(cp) == Character.UnicodeScript.HAN)
.count();
return String.format("Total %d chars, including %d CJK chars", text.length(), cjk);
}
}
@Tool의 설명은 AI를 위해 작성됩니다 — AI가 적절한 순간에 도구를 호출할지 여부를 직접 결정합니다. "이 메서드가 무엇을 하는지"보다 "언제 사용하는지"를 쓰는 것이 훨씬 유용합니다. 후자는 AI가 판단해야 할 일이기 때문입니다.
두 번째는 manifest.json으로, 플러그인이 무엇인지 설명합니다:
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"description": "One sentence explaining what this plugin does for users, shown on the marketplace card.",
"author": "Your Name or Team",
"homepage": "https://example.com",
"permissions": [],
"tools": [
{
"name": "countChinese",
"description": "Count characters in text",
"permissions": []
}
],
"backendJars": ["my-plugin-1.0.0.jar"]
}
도구 이름은 양쪽에서 일치해야 합니다: tools[].name 은(는) Java 메서드 이름과 같아야 합니다. 다르면 도구가 등록되지 않아 AI가 볼 수 없습니다.
그런 다음 패키징하세요:
mvn package
mkdir -p dist && cp target/my-plugin-1.0.0.jar manifest.json dist/
cd dist && zip -r ../my-plugin-1.0.0.zip . && cd ..zip에는 파일 자체가 포함되어야 합니다. 추가 디렉터리를 중첩하지 마세요. 압축을 풀면 폴더가 아니라 manifest.json이 바로 나타나야 합니다.
제출하기 전에 자신의 컴퓨터에서 시험해 보세요
심사를 기다릴 필요가 없습니다. dist/ 디렉터리를 로컬 플러그인 폴더에 넣고 AI WorkDeck을 재시작하세요:
# macOS / Linux
~/.aiworkdeck/plugins/my-plugin/플러그인이 Plugin Marketplace → Installed 아래에 표시되어야 합니다. 활성화한 후, 해당 도구를 사용해야 하는 대화에서 질문을 던져 AI가 도구를 호출하는지 확인하세요. 호출하지 않는다면 대부분 @Tool 설명이 언제 사용해야 하는지 제대로 설명하지 못하고 있는 것입니다.
manifest.json 각 필드의 의미
| 필드 | 설명 |
|---|---|
id | 전역적으로 고유해야 하며, 소문자, 숫자, 하이픈만 사용할 수 있습니다. 한 게시되면 절대 변경할 수 없습니다. 업그레이드 시 "동일한 플러그인"을 식별하는 기준이 되기 때문입니다. |
version | 시맨틱 버전입니다. 모든 제출은 이전 버전보다 높아야 하며, 그렇지 않으면 거부됩니다. |
name / description | 마켓플레이스 카드에 표시됩니다. 구현 방식이 아니라 사용자에게 어떤 가치를 주는지 설명하세요. |
author / homepage | 작성자와 프로젝트 홈페이지입니다. 필수는 아니지만 권장합니다. 사용자는 이를 통해 신뢰성을 판단합니다. |
permissions | 이 플러그인이 사용하는 권한입니다. 자세한 내용은 다음 섹션을 참고하세요. |
tools | 도구 목록입니다. name은 Java 메서드 이름과 일치해야 하며, 목적이 명확히 드러나도록 description을 작성하세요. |
backendJars | 패키지 루트 기준 상대 경로의 JAR 파일 이름입니다. 패키지 외부를 가리키는 ../ 경로는 허용되지 않습니다. |
permissions: 정직하게 선언하세요 — 검토 시 교차 확인
네 가지 선택 값이 있으며, 사용하는 것만 선언하세요:
file_read프로젝트 파일 읽기file_write파일 생성, 수정 또는 삭제network외부 네트워크 접근editor문서 편집기 조작
이것은 샌드박스가 아닙니다
플러그인은 호스트 앱과 동일한 프로세스에서 실행되므로, 선언되지 않은 동작을 기술적으로 차단할 수 없습니다. 권한을 전혀 선언하지 않은 도구도 파일을 읽을 수 있습니다. 이 선언은 런타임 제한이 아니라 검토 근거: JAR 정적 스캔 결과와 교차 확인됩니다. 네트워크를 선언하지 않으면서 네트워크 API를 참조하거나, 그 반대의 경우에는 제출이 거부됩니다.
검토 항목
모든 버전은 사람이 검토하며, 보통 1~2 영업일 내에 완료됩니다. 먼저 자동 스캔이 실행되고, 그 보고서가 검토자의 책상 위에서 권한 선언과 나란히 놓이게 됩니다.
다음은 즉시 거부됩니다:
- 선언된 권한과 실제 호출된 API가 일치하지 않는 경우
- 커스텀 TrustManager 또는 인증서 검증을 우회하는 기타 방법
- 하드코딩된 IP 주소, 또는 평문 HTTP로 전송되는 데이터
- 호스트 내부 객체(데이터베이스 연결, 설정 서비스 등)에 대한 리플렉션
- 난독화 또는 패킹된 코드, 또는 코드 동작을 은폐하는 모든 것
- 이전 버전보다 높지 않은 버전 번호
승인되면 플랫폼이 개인 키로 전체 패키지에 서명하고, 클라이언트는 설치 시 서명을 검증합니다. 그 후에는 당사를 포함한 누구도 재서명 없이는 패키지 내용을 변경할 수 없습니다.
출시 후 문제가 발견되면 해당 버전을 폐기(revoke)합니다. 클라이언트는 폐기 목록을 받아 해당 버전을 자동으로 비활성화하고 사용자에게 알립니다.
사용자에 대한 약속이자 개발자에 대한 제약
저희 사용자는 변호사이며, 그들의 컴퓨터에는 고객의 기밀 자료가 저장되어 있습니다. 플러그인은 호스트 앱과 동일한 접근 권한을 갖게 되는데, 이는 플러그인 자체에 필요한 수준을 훨씬 넘어서는 것입니다.
그래서 심사는 엄격한 쪽으로 진행되며, 거절 시에는 그 사유가 함께 안내됩니다. 플러그인이 민감해 보이는 기능을 실제로 필요로 하는 경우, 제출 시 메모에 그 이유를 설명해 주시면 심사가 훨씬 빨라집니다.
웹 플러그인: UI를 HTML/JS로 제작
원하는 것이 인터페이스, 즉 폼, 조회 도구, 대시보드라면 Java가 필요하지 않습니다. 패키지에 web/ 디렉터리를 넣고(일반 정적 HTML/JS/CSS, 진입점은 web/index.html) 매니페스트의 frontendEntry에서 해당 경로를 가리키면 됩니다. JAR 없이 배포할 수도 있습니다.
여기서 권한은 실제로 적용됩니다. 데스크톱 앱은 웹 플러그인을 샌드박스 처리된 iframe에 로드하며, 의도적으로 동일 출처(same-origin)가 아니게 만듭니다. 플러그인 스크립트는 호스트 세션을 볼 수 없고, 모든 기능은 postMessage 브리지를 통해서만 전달되며, 호스트는 각 호출을 매니페스트 권한과 대조하여 검증합니다. JAR 플러그인과 달리 이 선언은 자기 소개가 아니라 실제 강제 경계입니다.
템플릿의 web/awd-plugin-sdk.js가 이 브리지를 감쌉니다. 핸드셰이크가 완료되기 전에는 아무것도 동작하지 않습니다:
| 메서드 | 파라미터 | 결과 | 권한 |
|---|---|---|---|
settings.open / settings.update | {} / {values} | {opened} / {} | user / project settings; installation settings stay in trusted host UI |
connections.list / connect / disconnect / request | {id, operation?, params?} | connection state / service JSON | network; host-held credentials and explicit developer trust |
mcp.servers / mcp.call | {} / {serverId, name, arguments} | {servers} / MCP CallToolResult | network + project access; public HTTPS Streamable HTTP |
billing.status / billing.checkout | {connectionId?} | {provider, active, plan?} / {opened} | host purchase entitlement or developer subscription; no automatic charge |
services.request / services.cancel | {service, operation, params, requestId} / {requestId} | operation result | explicit service catalog; per-operation permission checks |
context.get | {} | { pluginId, projectId, language, theme, themeTokens } | - |
files.list | {} | { files: [{ path, name, size }] } | file_read |
im.downloadAttachment | { conversationId, attachmentId } | { started, name } | im (server-side attachment ACL check; no project write permission required) |
notifications.publish | { eventKey, title, body?, actionState?, target? } | notification | notifications (current account inbox; target opens only this plugin) |
notifications.resolve | { eventKey, state } | notification | notifications (updates only this plugin’s notifications; marking read never completes business actions) |
im.request | { action, params } | object | im (host 0.61.0+; fixed messaging actions, project archiving also requires file_write) |
chat.addFile | { path } | {} | file_read (host 0.61.0+; attaches material without auto-sending) |
files.prepareDrag | { path } | { fileId, dragToken, dragType, expiresAt, ... } | file_read (host 0.61.0+; short-lived single-use drag ticket) |
files.endDrag | {} | {} | file_read (host 0.61.0+) |
files.read | { path } | { path, content, truncated } | file_read |
ui.toast | { message } | {} | - |
storage.get | { key } | { key, value } | - |
storage.set | { key, value } | {} | - |
evidence.link | { anchor: { selection: true } | { quote }, docPath?, targets: [{ path, locator?, relation?, method?, note? }] } | { linkKey, targetIds } | editor |
evidence.list | { docPath?, path?, sectionPath?, status? } | { links: [{ linkKey, docPath, anchorText, sectionPath, status, targets }] } | file_read |
evidence.locate | { linkKey, targetId? } | {} | editor |
tools.invoke | { name, args? } | { output } | - (tool must be declared in this plugin’s manifest) |
chat.send | { prompt } | {} | - (up to 4,000 characters) |
ui.openFile | { path } | {} | file_read |
doc.exec | { action, params? } | { result } | editor (host 0.27.4+; action restricted to safe doc_/sheet_/slide_ subset) |
doc.active | {} | { fileId, kind } | editor (host 0.27.4+) |
events.subscribe | { events: ["files.changed" | "selection.changed" | "project.switched"] } | { subscribed } | per event (host 0.27.4+) |
events.unsubscribe | { events } | { subscribed } | - |
ai.request | { prompt, system?, purpose? } | { text, modelId } | ai (host 0.27.4+; 16,000 chars, 10 calls/min, billed via user Credits) |
settings.get | { key } | { key, value } | - (host 0.28+; reads non-secret keys declared in top-level manifest settings) |
<script src="awd-plugin-sdk.js"></script>
<script>
const ctx = await awd.ready(); // { pluginId, projectId, language, theme, themeTokens }
const files = await awd.files.list(); // requires file_read
const doc = await awd.files.read(files[0].path); // { path, content, truncated }
await awd.ui.toast('Completed');
await awd.storage.set('draft', { title: 'x' }); // plugin-scoped KV, capped at 64 KB
</script>에러는 거부된(rejected) 프로미스로 err.code와 함께 전달됩니다. 매니페스트에 호출에 필요한 권한이 선언되어 있지 않으면 permission_denied, 호스트가 인식하지 못하는 메서드이면 unknown_method가 발생합니다. files.read는 텍스트를 5MB로 제한하며(초과 시 truncated가 true가 됨), storage는 플러그인당 총 64KB로 제한합니다.
테마 채널 (v2.6)
이제 핸드셰이크 컨텍스트에 theme('light'|'dark')와 themeTokens(--awd-* CSS 변수 테이블)가 포함되며, 호스트는 이후 모든 전환 시 { type: 'theme', theme, tokens }을 푸시합니다. SDK는 이를 자동으로 적용합니다. data-theme 설정, awd-theme-light/awd-theme-dark 클래스 토글, 각 토큰을 CSS 사용자 지정 속성으로 기록하는 방식으로, 플러그인 CSS는 JS 없이 var(--awd-surface)만 사용하면 됩니다. 스크립트 기반 동작이 필요하면 awd.theme.get()으로 현재 값을 읽고 awd.theme.onChange(cb)로 변경을 구독하세요. 호스트 시뮬레이터 헤더에는 라이트/다크 토글 버튼이 있어 데스크톱 앱을 설치하지 않고도 두 테마를 모두 테스트할 수 있습니다.
템플릿에는 호스트 시뮬레이터인 dev/host-simulator.html도 포함되어 있습니다. 이 파일은 데스크톱 호스트를 흉내 내어 핸드셰이크를 수행하고, 모든 메서드에 가짜 데이터로 응답하며, 각 브리지 메시지를 기록합니다. 로컬 정적 서버를 실행하면 AI WorkDeck을 설치하지 않고도 브라우저에서 개발할 수 있습니다.
python3 -m http.server 8000
# -> http://localhost:8000/dev/host-simulator.htmlfile://로 직접 열면 대개 빈 화면만 표시됩니다. 브라우저는 file://에서 샌드박스 처리된 하위 문서 로드를 거부하기 때문입니다. 서버가 URL을 재작성하지도 않도록 주의하세요. npx serve는 기본적으로 clean URL을 활성화하여 /web/index.html을 /web으로 리다이렉트하므로 상대 경로 SDK 참조가 404 오류를 냅니다.
SDK 1.6 platform APIs (new host preview)
The Web download includes a standalone Node.js 20+ CLI. Keep scripts/ and sdk/plugin-sdk/ in their relative locations. Test UI in the simulator, then permissions, project switching, disabling, cancellation and reconnection in a compatible isolated host. Example domains are placeholders, not working services.
Declare sidebar/workspace views with project/global scope; one view per placement and an existing web/ entry. Sidebar docks are left/right/bottom. Settings default to installation scope; user/project settings may be changed by the plugin. Installation settings remain in trusted host UI. Secrets never return to the iframe; use connections for per-user credentials.
Connections declare a fixed public HTTPS origin and fixed GET/POST operations. Authentication is none, Bearer or public-client OAuth2 PKCE. connect opens trusted host UI; tokens stay in the host. Never pass arbitrary URLs, authorization headers or host setting keys. BYO services and shared host AI/speech channels are separate choices with their own permissions and usage constraints.
OAuth uses Authorization Code with S256 PKCE. A local host uses its actual port at http://127.0.0.1:<port>/api/plugins/oauth/callback; server deployments must set ai.plugins.oauth-redirect-uri to a trusted HTTPS callback and register it with the provider. Expired credentials without refresh or credentials rejected with 401/403 return needsReconnect and require host-side login again.
MCP currently supports public HTTPS Streamable HTTP only: no stdio, local processes, private addresses or redirects. Declare network, every server and tool; remote names and schemas must match. Input roots are objects. Supported schema keywords are type/title/description, object properties/required/boolean additionalProperties, array items and string enum. Types include boolean/integer/number. No $ref, oneOf, nullable or other keywords. Limits: depth 8, 64 properties per object and 64 tools per server. connectionId references a same-origin connection in this plugin. Legacy credentialKey only references a declared installation secret.
Development installs require explicit administrator service trust in the host settings page. Trust is bound to the full manifest SHA256 and expires when it changes. Packages must not contain .awd-dev, .awd-service-trust or .awd-bundled markers. Trust does not unlock development JARs or local processes.
speech, tasks and clipboard permissions grant only catalogued services.request operations; this is not an arbitrary backend proxy. services.cancel cancels this plugin’s requests. Handle unsupported hosts, missing permissions and failures explicitly; do not silently switch to external or paid services.
Platform billing currently covers plugin purchase entitlement only; submitted prices are one-time purchases. There is no platform per-call collection or revenue-sharing API. Developer-owned subscriptions may declare connections.billing status and checkout. An opened checkout is not a successful payment.
Host billing.status refreshes the current account entitlement and returns refreshed; false means the refresh did not succeed and active may come from a still-valid cache, not a fresh payment confirmation. Checkout waits for the browser-opening result and rejects failures. Test actual host login, token expiry, subscription status, MCP errors, account switching and shared/BYOK routing; a synthetic provider verifies host integration, not live OAuth or payment certification.
Complete the free developer eligibility review and generate a submission token in the developer center. Supply it through the local AWD_PLUGIN_TOKEN environment variable. Never put it in source, Agent prompts or packages. Valid submissions remain pending human review and signing; passing local validation is not marketplace approval.
node scripts/plugin-dev.mjs validate .
node scripts/plugin-dev.mjs package . --out ../my-plugin.zip
# Start the synthetic UI simulator, then open /dev/host-simulator.html
python3 -m http.server 8000
# Explicit development installation: select an isolated imported project/folder first.
# Replace PORT with the isolated host port. AWD_SESSION_ID is supplied locally if required.
# Do not paste credentials into source or an Agent prompt.
node scripts/plugin-dev.mjs install --endpoint http://127.0.0.1:PORT --project-id PROJECT_ID --folder-id FOLDER_ID
# Explicit marketplace submission only; provide AWD_PLUGIN_TOKEN locally.
node scripts/plugin-dev.mjs submit . --endpoint https://www.aiworkdeck.com/api/plugins --price-cents 0
node scripts/plugin-dev.mjs submissions --endpoint https://www.aiworkdeck.com/api/plugins{
"id": "my-service-plugin",
"name": "Service panel",
"version": "1.0.0",
"description": "SDK 1.6 host preview example",
"author": "Example developer",
"frontendEntry": "web/index.html",
"permissions": [
"network",
"ai",
"speech",
"tasks",
"clipboard"
],
"settings": [
{
"key": "language",
"label": "Language",
"type": "string",
"scope": "user",
"default": "zh-CN"
},
{
"key": "summaryStyle",
"label": "Summary style",
"type": "string",
"scope": "project",
"default": "brief"
}
],
"contributes": {
"views": [
{
"id": "service-panel",
"name": "Service panel",
"entry": "web/index.html",
"placement": "sidebar",
"scope": "project",
"defaultDock": "left",
"allowedDocks": [
"left",
"right",
"bottom"
]
}
],
"connections": [
{
"id": "service",
"label": "My service",
"baseUrl": "https://api.example.com",
"auth": {
"type": "bearer"
},
"operations": {
"summarize": {
"method": "POST",
"path": "/v1/summarize"
},
"status": {
"method": "GET",
"path": "/v1/subscription"
}
},
"billing": {
"statusOperation": "status",
"checkoutUrl": "https://example.com/subscribe"
}
}
],
"mcpServers": [
{
"id": "remote",
"transport": "streamable-http",
"url": "https://api.example.com/mcp",
"connectionId": "service",
"tools": [
{
"name": "summarize",
"description": "Summarize supplied text",
"inputSchema": {
"type": "object",
"properties": {
"text": {
"type": "string"
}
},
"required": [
"text"
],
"additionalProperties": false
}
}
]
}
]
}
}Complete task to give your Agent
Implement this requirement using the downloaded AI WorkDeck Web plugin template: [feature, inputs, outputs, acceptance criteria].
Read README.md, sdk/plugin-sdk/awd-plugin-sdk.d.ts and manifest.schema.json first. Use minimum permissions. SDK 1.6 platform features require the unreleased new host; simulator success does not prove installed-client support.
Edit only this plugin. Preserve scripts/ and sdk/plugin-sdk/ layout. Do not modify the SDK to bypass checks. Support --awd-* theme tokens, keyboard interaction, narrow panels and light/dark themes.
Declare views and installation/user/project settings as needed. Credentials must use trusted host connections; never put tokens in the iframe, logs, repository or zip.
Choose shared host AI/speech/tasks/clipboard with the required permissions, or fixed HTTPS BYO operations with network permission. MCP must use public HTTPS Streamable HTTP and explicitly declared tools with the supported schema subset. No stdio, private hosts, arbitrary URLs or system settings.
Use synthetic simulator data to verify success, permission denial, disconnect, retry and cancel. Do not access customer data, real login or paid services. Use only my designated isolated project for host installation; ask the host administrator to grant service trust in settings after manifest changes.
Run node scripts/plugin-dev.mjs validate . and node scripts/plugin-dev.mjs package . --out ../my-plugin.zip. Verify no .env, credentials, private keys or host trust markers. Report changes, checks, unverified boundaries and zip location.
Only submit when I explicitly request it, using a locally provided AWD_PLUGIN_TOKEN without printing it. Report pending review accurately; never claim approval, payment or release.