플러그인 개발 문서
1. 플러그인 패키지 구조
플러그인은 디렉터리로 만들고 ZIP으로 묶어 업로드합니다. 최소 구조는 다음과 같습니다.
my_plugin/
├── plugin.json # 플러그인 선언(필수)
├── __init__.py # 빈 파일이면 충분(필수)
├── runner.py # 진입 로직(필수)
├── provider.py # provider 계약(필수, 로컬 전용 플러그인은 비워도 됨)
├── credentials.py # secrets_schema 선언 시 필수
└── README.md # 문서(선택)ZIP 루트에 plugin.json을 직접 포함하세요. 바깥에 별도 래퍼 디렉터리를 추가하지 마세요.
2. plugin.json 계약
Manifest는 loader의 엄격한 계약을 따릅니다. 아래 최소 예제는 저장소 계약 테스트로 검증되며, 업로드 시 plugin_id와 package_id가 비공개 네임스페이스로 바뀝니다.
{
"plugin_id": "store.sandbox.example.text_uppercase",
"node_type": "text_uppercase",
"package_id": "store.sandbox.example.text_uppercase",
"version": "1.0.0",
"runtime_contract_version": "1.0.0",
"manifest_schema_version": "1.0.0",
"execution": "server",
"title": "文本转大写",
"category": "文本处理",
"description": "把输入文本转换为大写。",
"available_services": [
"workflow"
],
"inputs": [
{
"id": "text",
"label": "输入文本",
"data_type": "Text",
"path": "input.text"
}
],
"outputs": [
{
"id": "result",
"label": "处理结果",
"data_type": "Text",
"path": "output.result"
}
],
"config_schema": {},
"pricing_schema": {
"pricing_mode": "fixed",
"unit_credits": 1,
"unit_label": "次"
},
"secrets_schema": [],
"provider_policy": {
"provider_key": "developer.local",
"provider_type": "local",
"auth_type": "none",
"timeout_ms": 30000,
"max_retries": 0
},
"capabilities_schema": {},
"style_schema": {
"icon": "type",
"accent": "#6366f1",
"theme": "indigo",
"node_width": 420
},
"asset_schema": {},
"failure_policy": {
"refund": {
"mode": "refund_on_failure"
}
},
"permissions": {},
"metadata": {
"capabilities": [
"workflow.text_transform"
]
},
"compatibility": {
"workflow_runtime": ">=1.0.0",
"service_slug": "workflow"
},
"dependencies": {
"plugins": [],
"python": [],
"providers": []
},
"independent": true,
"entrypoint": {
"module": "runner",
"callable": "run_text_uppercase"
},
"author": {
"name": "示例开发者"
}
}ui_schema, connection_schema, output_schema, layout_schema, display_schema를 직접 작성하지 마세요. loader가 포트, 설정, 스타일 선언에서 생성합니다. 알 수 없는 필드는 거부되며 확장에는 x_ 접두사를 사용하세요.
타사 플러그인의 현재 호출 가격, 최소 가격, 개발자와 플랫폼의 배분 비율은 개발자 센터에서 불러온 현재 계약을 기준으로 합니다. 문서의 과거 수치로 정산을 구현하지 마세요.
3. runner.py 진입점
진입점은 async 함수입니다. 설정은 request["node"]["config"], 연결 입력은 request["node"]["input"]에 있습니다. 반환값에는 manifest 출력 또는 표준 메시지만 포함할 수 있습니다.
from typing import Any
async def run_text_uppercase(request: dict[str, Any]) -> dict[str, Any]:
node = request.get("node", {}) if isinstance(request.get("node"), dict) else {}
config = node.get("config", {}) if isinstance(node.get("config"), dict) else {}
inputs = node.get("input", {}) if isinstance(node.get("input"), dict) else {}
text = str(inputs.get("text") or config.get("text") or "")
return {
"result": text.upper(),
"message": "처리 완료",
"message_code": "TEXT_UPPERCASE_COMPLETED",
}outputs 또는 파생 output_schema에 선언되지 않은 업무 필드를 반환하면 계약 오류가 발생합니다. runner.py에서 플랫폼 환경 변수나 비밀 정보를 읽지 마세요.
4. 보안 제한(검토 중 차단)
플랫폼과 다른 사용자를 보호하기 위해 플러그인 코드에서 다음 동작을 금지하며 자동 검토에서 차단합니다.
- os / sys / subprocess / socket / shutil 같은 시스템 모듈 가져오기
- os.environ, 환경 변수 또는 플랫폼 비밀 정보에 접근하기
- eval / exec / open / __import__ 같은 위험한 함수 호출하기
- 로컬 파일 시스템을 읽거나 쓰고 자식 프로세스를 시작하기
허용: httpx(HTTP 호출), json / typing / datetime / re / base64 / hashlib / math / decimal / uuid / collections / itertools, app.core.errors, app.plugins.toolkit. 패키지 모듈은 상대 import를 사용하세요. 목록 밖 import가 플랫폼 기능을 자동으로 부여하지는 않습니다.
API 키 같은 사용자 자격 증명은 secrets_schema로 선언하고 permissions.secrets에서 명시적으로 허용하세요. config_defaults, 소스, README, ZIP 예제에 비밀 정보를 넣지 마세요.
5. 제출 및 검토 흐름
- ZIP을 만들고개발자 제출 안내에서 비공개 샌드박스(본인만 볼 수 있음)로 업로드합니다.
- 비공개 로드 테스트를 실행합니다. 프로덕션 loader로 manifest, 진입점, 포트, 가격, 자격 증명 계약을 검증하며 runner를 실행하거나 공개 registry에 쓰지 않습니다.
- 검토 및 제출을 실행하여 결정적 계약 검증, AST 기능 스캔, Bandit 고위험 스캔을 수행합니다. AI 사전 검토는 포함되지 않습니다.
- 모든 검사를 통과하면 manual_review 상태로 사람 검토에 들어갑니다. 실패하면 rejected가 되며 다시 업로드, 테스트, 제출해야 합니다.
- 승인 후 approved 영역에 복사하고 핫 리로드합니다. 로드 성공 후에만 approved가 됩니다.
6. 수익 및 배분
게시 후 실제 유료 호출이 발생하면 호출 크레딧은 미정산 상태가 되고 정산 주기 후 개발자 잔액에 반영됩니다. 실패 호출, 환불 및 과거 정산은 변경 불가능한 서버 원장을 따릅니다.
개발자 배분, 플랫폼 비율, 최소 가격은 수익 API의 권위 있는 계약 필드를개발자 센터에서 표시합니다. 이 페이지는 업무 상수를 복제하지 않아 계약 변경 시 이중 기준을 피합니다.