@morit/cli 1.3.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -5
- package/assets/plugin_contract.json +33 -8
- package/bin/morit.js +0 -0
- package/package.json +1 -1
- package/src/cli.js +5 -0
- package/src/preview.js +254 -17
- package/src/workspace.js +218 -19
- package/assets/docs/README.md +0 -107
- package/assets/docs/ai-response-and-timeline.md +0 -203
- package/assets/docs/ai-skill-and-docx-workflow.md +0 -83
- package/assets/docs/app-builder.md +0 -56
- package/assets/docs/authentication.md +0 -140
- package/assets/docs/components.md +0 -216
- package/assets/docs/design-tokens-responsive.md +0 -171
- package/assets/docs/docs-index.json +0 -94
- package/assets/docs/examples-notion.md +0 -83
- package/assets/docs/examples-school-life.md +0 -79
- package/assets/docs/getting-started.md +0 -132
- package/assets/docs/information-hierarchy.md +0 -81
- package/assets/docs/instances-and-connectors.md +0 -93
- package/assets/docs/lifecycle-and-api.md +0 -169
- package/assets/docs/local-cli.md +0 -125
- package/assets/docs/manifest.md +0 -234
- package/assets/docs/packaging-and-testing.md +0 -121
- package/assets/docs/permissions-and-data.md +0 -149
- package/assets/docs/platform-compatibility.md +0 -62
- package/assets/docs/plugin-storage.md +0 -175
- package/assets/docs/project-structure.md +0 -102
- package/assets/docs/remote-mcp.md +0 -158
- package/assets/docs/school-life-privacy.md +0 -55
- package/assets/docs/screens-layout-navigation.md +0 -95
- package/assets/docs/sdk-and-mcp.md +0 -199
- package/assets/docs/tool-and-skill.md +0 -172
- package/assets/docs/troubleshooting.md +0 -121
- package/assets/docs/ui-extensions.md +0 -75
- package/assets/docs/ui-runtime-v2.md +0 -343
- package/assets/docs/verification.md +0 -133
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
# Response UI와 Agent Timeline
|
|
2
|
-
|
|
3
|
-
Response UI는 capability 결과를 AI 메시지 안에서 시각화합니다. 대화 아래의 별도 카드 목록이나
|
|
4
|
-
overflow 메뉴 밑에 떨어뜨리지 않고, 해당 AI 메시지에 속한 하나의 response container 안에
|
|
5
|
-
제목·내용·행동을 함께 렌더링합니다.
|
|
6
|
-
|
|
7
|
-
## Response extension
|
|
8
|
-
|
|
9
|
-
```json
|
|
10
|
-
{
|
|
11
|
-
"id": "school_life.day_response",
|
|
12
|
-
"point": "response",
|
|
13
|
-
"title": "오늘 학교 생활",
|
|
14
|
-
"order": 10,
|
|
15
|
-
"permissions": [],
|
|
16
|
-
"config": {
|
|
17
|
-
"ui_schema": 2,
|
|
18
|
-
"a2ui": {
|
|
19
|
-
"version": "v0.9",
|
|
20
|
-
"description": "수업 정보가 실제 답변에 필요하고 표시할 항목이 있을 때만 사용합니다.",
|
|
21
|
-
"schema": {
|
|
22
|
-
"type": "object",
|
|
23
|
-
"properties": {
|
|
24
|
-
"timetable": {
|
|
25
|
-
"type": "array",
|
|
26
|
-
"items": {"type": "object"},
|
|
27
|
-
"minItems": 1,
|
|
28
|
-
"maxItems": 12
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
"required": ["timetable"],
|
|
32
|
-
"additionalProperties": true
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
"theme": {"density": "compact"},
|
|
36
|
-
"data_sources": [
|
|
37
|
-
{
|
|
38
|
-
"id": "result",
|
|
39
|
-
"capability": "school_life.schedule.lookup",
|
|
40
|
-
"trigger": "manual",
|
|
41
|
-
"query": "",
|
|
42
|
-
"arguments": {}
|
|
43
|
-
}
|
|
44
|
-
],
|
|
45
|
-
"view": {
|
|
46
|
-
"type": "column",
|
|
47
|
-
"props": {"spacing": 10},
|
|
48
|
-
"children": [
|
|
49
|
-
{
|
|
50
|
-
"type": "text",
|
|
51
|
-
"props": {"text": "{{data.result.summary}}", "style": "heading"}
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"type": "timeline",
|
|
55
|
-
"props": {"source": "data.result.data.timetable", "empty_text": "수업 정보가 없습니다."},
|
|
56
|
-
"children": [
|
|
57
|
-
{
|
|
58
|
-
"type": "text",
|
|
59
|
-
"props": {"text": "{{item.period}}교시 · {{item.subject}}"}
|
|
60
|
-
}
|
|
61
|
-
]
|
|
62
|
-
}
|
|
63
|
-
]
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Response point도 일반 Runtime v2와 같은 node, theme, app bar, navigation, binding, action 계약을
|
|
70
|
-
사용합니다. Host가 선택·검증한 capability 결과는 `data` namespace에 주입되므로 같은 결과를 얻기
|
|
71
|
-
위해 화면 mount 시 외부 요청을 반복하지 않습니다. 후속 refresh나 사용자가 누른 action에만 추가
|
|
72
|
-
capability를 호출합니다. 기존 `a2ui` 선언이 없는 Response extension은 지원하지 않습니다.
|
|
73
|
-
|
|
74
|
-
## AI 선택과 Host 검증
|
|
75
|
-
|
|
76
|
-
서버는 모든 Tool 결과에 UI를 일괄 생성하거나 데이터 key로 컴포넌트를 추측하지 않습니다. 최종
|
|
77
|
-
답변 모델이 현재 답변에 실제 도움이 되는 후보의 `candidate_id`와 `component`만 선택합니다. 모델은
|
|
78
|
-
컴포넌트 데이터나 임의 ID를 만들 수 없고, 실제 Tool 결과는 Host가 선택 뒤 주입합니다.
|
|
79
|
-
|
|
80
|
-
선택 뒤 Host는 다음을 모두 통과한 항목만 A2UI v0.9 DataPart로 저장하고 해당 AI 메시지에 붙입니다.
|
|
81
|
-
|
|
82
|
-
- 실행 상태가 `completed` 또는 `partial`이고 실제 값이 있음
|
|
83
|
-
- 질문·최종 답변과 capability 결과가 관련됨
|
|
84
|
-
- 같은 데이터의 Response UI가 이미 선택되지 않음
|
|
85
|
-
- 기본 컴포넌트별 필수 값과 URL 형식이 유효함
|
|
86
|
-
- Plugin 컴포넌트의 schema, A2UI version, 설치 상태, Instance 권한과 capability 연결이 유효함
|
|
87
|
-
|
|
88
|
-
빈 데이터, 실패한 Tool, 무관하거나 중복된 결과, 불확실한 선택은 UI를 만들지 않습니다. 선택 모델
|
|
89
|
-
호출이나 렌더링이 실패해도 일반 텍스트 답변은 그대로 완료됩니다. 진행·실패 상태는 Agent Timeline에
|
|
90
|
-
표시하며 의미 없는 loading/failed Response 카드를 미리 만들거나 DB에 남기지 않습니다.
|
|
91
|
-
|
|
92
|
-
## 기본 A2UI Component Catalog
|
|
93
|
-
|
|
94
|
-
| component | 표시 조건 |
|
|
95
|
-
|---|---|
|
|
96
|
-
| `weather` | 현재 기온·상태 또는 예보가 하나 이상 유효함 |
|
|
97
|
-
| `chart` | label과 수치가 있는 행이 2개 이상임; `bar`, `line`, `pie`, `scatter` |
|
|
98
|
-
| `image_gallery` | HTTPS 이미지와 원문 URL·출처명이 함께 있음 |
|
|
99
|
-
| `exchange_rate` | 기준 통화, 상대 통화, 유한한 환율 값이 있음 |
|
|
100
|
-
| `article_list` | 제목·출처·원문이 있는 서로 다른 기사 2개 이상 |
|
|
101
|
-
| `article_card` | 제목·출처·게시 시각·요약·본문 일부·이미지·원문이 모두 있음 |
|
|
102
|
-
| `file_result` | 실제 Morit item ID 또는 검증 가능한 다운로드 URL이 있음 |
|
|
103
|
-
| `image_preview` | 실제 image item 또는 검증 가능한 preview URL이 있음 |
|
|
104
|
-
| `video_preview` | 실제 video item/URL과 thumbnail URL이 함께 있음 |
|
|
105
|
-
|
|
106
|
-
각 renderer도 같은 필수 값을 다시 확인하고, 값이 바뀌거나 손상되면 해당 컴포넌트만 fallback합니다.
|
|
107
|
-
|
|
108
|
-
## Plugin 컴포넌트의 동적 등록
|
|
109
|
-
|
|
110
|
-
활성 Instance의 `point: "response"` extension은 capability 실행 시
|
|
111
|
-
`plugin.<extension-id>` 이름으로 현재 A2UI catalog에 동적 등록됩니다. AI에게는 Host가 확인한 ID,
|
|
112
|
-
설명, version, schema만 노출됩니다. `a2ui.schema`는 root `object`인 안전한 JSON Schema 부분집합이며
|
|
113
|
-
`type`, `properties`, `required`, `additionalProperties`, `items`, `enum`, 문자열·숫자·배열 bound를
|
|
114
|
-
지원합니다. schema는 최대 16 KiB, 깊이 6이며 object/list는 각 64개로 제한됩니다.
|
|
115
|
-
|
|
116
|
-
Plugin Response는 Runtime v2의 전체 layout, surface, theme, image, action과 binding을 사용할 수
|
|
117
|
-
있습니다. 단, extension 권한은 Manifest grant의 부분집합이어야 하고 `data_sources` 중 하나가 실제
|
|
118
|
-
실행 capability를 가리켜야 합니다. 패키지 설치, CLI validate, Host 로드, Flutter parse와 표시 직전
|
|
119
|
-
검증이 같은 규칙을 사용합니다.
|
|
120
|
-
|
|
121
|
-
## 한 container의 정보 순서
|
|
122
|
-
|
|
123
|
-
1. 결과를 설명하는 짧은 제목 또는 summary
|
|
124
|
-
2. 사용자가 요청한 핵심 데이터
|
|
125
|
-
3. 출처·기간·갱신 시점 같은 보조 정보
|
|
126
|
-
4. 필요한 후속 행동 1~2개
|
|
127
|
-
|
|
128
|
-
복사, 다시 시도, 다운로드 같은 메뉴도 같은 container의 Host chrome에 속합니다. 각 section을
|
|
129
|
-
별도 떠 있는 card로 만들지 않습니다. 긴 결과는 list limit과 상세 화면 이동을 사용합니다.
|
|
130
|
-
|
|
131
|
-
## 대화 UI를 깨뜨리지 않는 제약
|
|
132
|
-
|
|
133
|
-
- 메시지 폭을 넘는 고정 width를 사용하지 않습니다.
|
|
134
|
-
- Response 내부에 자체 채팅 입력창을 만들지 않습니다.
|
|
135
|
-
- 무한 높이 목록 대신 요약과 상세 화면 이동을 제공합니다.
|
|
136
|
-
- background refresh가 대화 scroll 위치를 바꾸지 않게 기존 높이와 데이터를 가능한 유지합니다.
|
|
137
|
-
- 렌더링 오류는 해당 Response만 텍스트 fallback으로 바꾸고 대화 전체를 종료하지 않습니다.
|
|
138
|
-
- accessibility 순서는 AI 본문 다음, Response 제목, 내용, 행동 순으로 유지합니다.
|
|
139
|
-
|
|
140
|
-
## Text fallback
|
|
141
|
-
|
|
142
|
-
Response UI를 표시할 수 없더라도 AI의 원래 텍스트 답변은 남아야 합니다. capability는 UI 전용
|
|
143
|
-
원시 JSON만 반환하지 말고 사람이 읽을 수 있는 `summary`를 함께 제공합니다. 빈 배열만 있거나
|
|
144
|
-
schema가 맞지 않으면 UI를 억지로 채우지 않습니다. 알 수 없는 node, 손상된 binding, 이미지 로드
|
|
145
|
-
실패는 전체 답변 성공을 숨기지 않습니다.
|
|
146
|
-
|
|
147
|
-
```json
|
|
148
|
-
{
|
|
149
|
-
"completed": true,
|
|
150
|
-
"summary": "오늘은 6교시이며 점심은 카레라이스입니다.",
|
|
151
|
-
"data": {
|
|
152
|
-
"timetable": [],
|
|
153
|
-
"meal": {}
|
|
154
|
-
},
|
|
155
|
-
"evidence": []
|
|
156
|
-
}
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## Agent Timeline
|
|
160
|
-
|
|
161
|
-
Agent Timeline은 모델의 숨겨진 추론이 아니라 사용자가 이해할 수 있는 실행 상태만 보여줍니다.
|
|
162
|
-
|
|
163
|
-
```text
|
|
164
|
-
요청을 확인하고 계획했어요
|
|
165
|
-
├─ 학교 정보를 확인했어요
|
|
166
|
-
├─ 오늘 시간표를 불러왔어요
|
|
167
|
-
└─ 결과를 정리했어요
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Timeline event에는 도구 이름이나 내부 stack trace 대신 작업 이름, 상태, 필요한 사용자 행동을
|
|
171
|
-
기록합니다. 상태는 대체로 다음과 같습니다.
|
|
172
|
-
|
|
173
|
-
- 진행 중
|
|
174
|
-
- 완료
|
|
175
|
-
- 재시도 중
|
|
176
|
-
- 사용자 입력 필요
|
|
177
|
-
- 일부 결과로 완료
|
|
178
|
-
- 실패
|
|
179
|
-
|
|
180
|
-
Tool 하나가 실패했지만 대체 경로로 결과를 만들었다면 전체 timeline을 실패로 표시하지 않습니다.
|
|
181
|
-
실패한 단계와 복구 결과를 함께 설명합니다.
|
|
182
|
-
|
|
183
|
-
## Code Interpreter 파일
|
|
184
|
-
|
|
185
|
-
Code Interpreter가 파일을 만들었다면 sandbox 내부 경로만 답변에 남기지 않습니다. 파일은 Host가
|
|
186
|
-
접근 가능한 artifact로 전달되고, AI 답변에는 실제 파일 이름·형식·크기와 다운로드 가능한 링크가
|
|
187
|
-
있어야 합니다. Response UI의 다운로드 action은 동일 artifact를 가리키며 존재하지 않는 경로나
|
|
188
|
-
가짜 링크를 생성하지 않습니다.
|
|
189
|
-
|
|
190
|
-
파일 생성 완료 조건은 [AI Skill과 파일 산출물](ai-skill-and-docx-workflow.md)에 정리되어 있습니다.
|
|
191
|
-
|
|
192
|
-
## 이미지 artifact
|
|
193
|
-
|
|
194
|
-
`image_generation`·`image_edit`의 PNG/JPEG/GIF/WebP artifact는 생성 중 placeholder에서 완료 후 답변
|
|
195
|
-
내 inline 이미지로 바뀝니다. 여러 장은 gallery로 넘기며 원본 비율을 유지한 contain 렌더링,
|
|
196
|
-
전체 화면 `InteractiveViewer`, 다운로드, Android 공유를 제공합니다. 대화 재진입 때도 저장된
|
|
197
|
-
`ai_tool_executions.result_summary.artifacts`를 다시 사용합니다.
|
|
198
|
-
|
|
199
|
-
Artifact URL은 conversation·execution·attachment 소유권을 확인한 뒤 5분 signed URL로 만들며 만료나
|
|
200
|
-
일시 실패 시 새 URL을 받아 두 번 시도합니다. 앱은 redirect를 따르지 않고 HTTPS(개발 loopback 제외),
|
|
201
|
-
MIME, magic byte, 실제 decode, 24 MiB, 4096 px, 16 MP를 확인합니다. SVG와 외부 실행 형식은 inline으로
|
|
202
|
-
열지 않고 기존 파일 카드로 fallback합니다. Plugin response의 URL 이미지는 앱이 직접 요청하지 않고
|
|
203
|
-
기존 인증 Host proxy와 `network` grant를 거치며 실패 시 이미지 단위 재시도를 제공합니다.
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
# AI Skill과 파일 산출물
|
|
2
|
-
|
|
3
|
-
Morit Plugin의 `kind: "skill"`과 Codex·Claude 같은 AI 에이전트의 Skill 파일은 이름이 같지만
|
|
4
|
-
역할이 다릅니다.
|
|
5
|
-
|
|
6
|
-
- Morit Plugin Skill: Host가 설치·권한·timeout 경계에서 실행하는 capability
|
|
7
|
-
- AI 에이전트 Skill: 에이전트가 작업 순서와 도구 사용법을 따르는 지침
|
|
8
|
-
|
|
9
|
-
에이전트 Skill을 사용해 플러그인을 만들더라도 결과 `.mplg`는 일반 SDK 계약과 동일하게 검증·서명됩니다.
|
|
10
|
-
|
|
11
|
-
## 파일 작업 완료 순서
|
|
12
|
-
|
|
13
|
-
문서, 표, 이미지, archive 등 파일을 만드는 capability나 AI 작업은 다음 순서를 지킵니다.
|
|
14
|
-
|
|
15
|
-
```text
|
|
16
|
-
요청과 입력 조사
|
|
17
|
-
→ 파일 생성·편집
|
|
18
|
-
→ 구조 검증
|
|
19
|
-
→ 가능하면 시각 검증
|
|
20
|
-
→ 원래 parser로 다시 열고 내용 확인
|
|
21
|
-
→ 최종 artifact 저장
|
|
22
|
-
→ 사용자에게 실제 링크와 요약 반환
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
“작업을 마쳤어요”만 답하거나 sandbox 내부 경로만 남기면 완료가 아닙니다. 존재하지 않는 `.docx`
|
|
26
|
-
경로나 아직 생성하지 않은 artifact를 최종 링크처럼 반환하지 않습니다.
|
|
27
|
-
|
|
28
|
-
## DOCX 예
|
|
29
|
-
|
|
30
|
-
DOCX는 Microsoft Open XML ZIP 구조입니다. 단순히 `.docx` 확장자를 붙이지 않습니다.
|
|
31
|
-
|
|
32
|
-
1. 요청한 목차, 표, 이미지, 스타일을 실제로 작성합니다.
|
|
33
|
-
2. ZIP entry와 `[Content_Types].xml`, document relationship을 확인합니다.
|
|
34
|
-
3. DOCX parser로 다시 열어 문단·표·이미지 수를 확인합니다.
|
|
35
|
-
4. 모든 페이지를 렌더링해 잘림, 빈 페이지, 겹침, 깨진 한글을 확인합니다.
|
|
36
|
-
5. 수정 후 parser와 렌더링을 다시 실행합니다.
|
|
37
|
-
6. 존재하고 0바이트가 아닌 최종 `.docx`만 artifact로 반환합니다.
|
|
38
|
-
|
|
39
|
-
다른 형식도 동일합니다. PDF는 모든 페이지, spreadsheet는 수식과 셀 type, image는 실제 크기와
|
|
40
|
-
디코딩, ZIP은 entry 경로와 traversal 안전성을 확인합니다.
|
|
41
|
-
|
|
42
|
-
## Plugin이 파일을 만드는 경우
|
|
43
|
-
|
|
44
|
-
Plugin은 `file_write` 권한을 요청하고 Host가 제공하는 artifact 경계로 결과를 전달합니다.
|
|
45
|
-
`sandbox_python` 응답의 `summary`, `data`, `evidence`에 존재하지 않는 `sandbox:/...` 링크를 만들지
|
|
46
|
-
않습니다. Host로 복사된 artifact ID나 내부 파일 링크 문법을 사용하고, AI 최종 답변에는 다음을
|
|
47
|
-
포함합니다.
|
|
48
|
-
|
|
49
|
-
- 파일 이름과 형식
|
|
50
|
-
- 사용자가 요청한 결과 요약
|
|
51
|
-
- 다운로드 또는 앱 내부 미리보기 링크
|
|
52
|
-
- 검증한 항목과 남은 제한
|
|
53
|
-
|
|
54
|
-
## `.mplg` 산출물
|
|
55
|
-
|
|
56
|
-
플러그인 개발도 파일 작업입니다.
|
|
57
|
-
|
|
58
|
-
```text
|
|
59
|
-
source 작성
|
|
60
|
-
→ validate
|
|
61
|
-
→ UI가 있으면 preview
|
|
62
|
-
→ signed build
|
|
63
|
-
→ package reopen·signature verify
|
|
64
|
-
→ 앱 설치·실행
|
|
65
|
-
→ 요청한 경로에 artifact 반환
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
source ZIP과 `.mplg`는 목적이 다릅니다. source ZIP은 편집용이고 `.mplg`는 설치용입니다. 둘을 같은
|
|
69
|
-
다운로드 이름이나 MIME으로 반환하지 않습니다.
|
|
70
|
-
|
|
71
|
-
플러그인의 기본 품질 게이트는 `validate → preview → build → verify`입니다. UI가 없는 package는
|
|
72
|
-
preview를 생략할 수 있지만 생략 이유를 결과에 기록합니다.
|
|
73
|
-
|
|
74
|
-
## 완료 체크
|
|
75
|
-
|
|
76
|
-
- 요청한 모든 파일이 실제로 존재하는가
|
|
77
|
-
- 파일 크기가 0보다 큰가
|
|
78
|
-
- 확장자와 내부 형식이 일치하는가
|
|
79
|
-
- 원래 parser로 다시 열리는가
|
|
80
|
-
- 모든 페이지/시트/entry를 확인했는가
|
|
81
|
-
- 미리보기와 다운로드가 같은 최종 artifact를 가리키는가
|
|
82
|
-
- sandbox 경로나 secret이 사용자 응답에 남지 않았는가
|
|
83
|
-
- 실패한 Tool이 있다면 복구·대체 결과와 제한을 설명했는가
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# 앱에서 플러그인 만들기
|
|
2
|
-
|
|
3
|
-
Plugin Builder는 개발 환경 없이 작은 Tool 또는 Skill을 만드는 경로입니다. Builder도 일반
|
|
4
|
-
`.mplg`를 생성하고 같은 설치·권한·삭제 흐름을 사용하지만, 안전하게 표현할 수 있는 범위를
|
|
5
|
-
의도적으로 제한합니다.
|
|
6
|
-
|
|
7
|
-
## 지원 범위
|
|
8
|
-
|
|
9
|
-
| 항목 | 지원 값 |
|
|
10
|
-
|---|---|
|
|
11
|
-
| capability kind | `tool`, `skill` |
|
|
12
|
-
| runtime | `text_stats`, `text_template` |
|
|
13
|
-
| UI 위치 | `card`, `screen`, `settings` 중 최대 3개 |
|
|
14
|
-
| 선택 기능 | Search Provider, Slash Command, background, notification |
|
|
15
|
-
| 권한 | `background`, `notifications` |
|
|
16
|
-
| credential | 최대 4개의 `api_token` 선언(현재 앱 화면은 고급 source 흐름에서 설정) |
|
|
17
|
-
| background 주기 | 15~10,080분 |
|
|
18
|
-
|
|
19
|
-
Builder는 ID, 이름, 설명, version, capability 제목과 runtime 설정을 입력받아 최소 Manifest와
|
|
20
|
-
Runtime v1 UI를 만듭니다. UI 코드를 생성하거나 외부 코드를 실행하지 않습니다.
|
|
21
|
-
|
|
22
|
-
## Builder가 적합한 예
|
|
23
|
-
|
|
24
|
-
- 입력 텍스트의 길이와 단어 수를 계산하는 Tool
|
|
25
|
-
- 정해진 템플릿으로 회의 메모를 정리하는 Skill
|
|
26
|
-
- settings의 작은 정적 목록을 Search Provider로 노출
|
|
27
|
-
- 정해진 문구를 일정 주기로 알리는 개인 알림
|
|
28
|
-
|
|
29
|
-
## 소스 프로젝트로 전환할 때
|
|
30
|
-
|
|
31
|
-
다음 중 하나가 필요하면 공식 CLI 프로젝트를 사용합니다.
|
|
32
|
-
|
|
33
|
-
- Runtime v2의 화면·내비게이션·theme·Response UI
|
|
34
|
-
- 외부 HTTPS API, OAuth, Remote MCP
|
|
35
|
-
- 여러 데이터 source와 동적 목록
|
|
36
|
-
- `sandbox_python` 데이터 처리
|
|
37
|
-
- child package와 dependency
|
|
38
|
-
- Cloud Secret, Connector retry/rate-limit
|
|
39
|
-
- package asset, 이미지, 복수 source file
|
|
40
|
-
|
|
41
|
-
[시작하기](getting-started.md)의 `@morit/cli plugin setup`으로 프로젝트를 만들고 Builder에서 정한
|
|
42
|
-
ID와 capability 이름을 유지하면 사용자가 기능을 다시 익힐 필요가 없습니다. 기존 설치를 업데이트할
|
|
43
|
-
때는 publisher와 서명 identity, plugin ID, semantic version 순서를 유지해야 합니다.
|
|
44
|
-
|
|
45
|
-
## 실제 확인
|
|
46
|
-
|
|
47
|
-
Builder 생성 성공만 확인하지 말고 다음을 앱에서 실행합니다.
|
|
48
|
-
|
|
49
|
-
1. 생성 직후 설치 목록에 나타나는지
|
|
50
|
-
2. 필요한 권한을 거부했을 때 기능이 실행되지 않는지
|
|
51
|
-
3. Tool/Skill과 Slash Command가 정확한 capability를 호출하는지
|
|
52
|
-
4. background/notification을 켜고 끌 수 있는지
|
|
53
|
-
5. 설정 변경 후 다시 열었을 때 유지되는지
|
|
54
|
-
6. 삭제 후 pending 알림과 credential이 정리되는지
|
|
55
|
-
|
|
56
|
-
고급 화면 개발은 [화면, 레이아웃, 내비게이션](screens-layout-navigation.md)부터 시작하세요.
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
# 외부 서비스 인증과 Cloud Secrets
|
|
2
|
-
|
|
3
|
-
플러그인 source와 `.mplg`에는 비밀 값이 들어가지 않습니다. package는 필요한 Credential·Connector·
|
|
4
|
-
Secret ID만 선언하고 실제 값은 사용자 Connection 또는 조직의 Cloud Secret에 저장합니다.
|
|
5
|
-
|
|
6
|
-
## 선택 기준
|
|
7
|
-
|
|
8
|
-
| 값의 소유자 | 사용 모델 | 예 |
|
|
9
|
-
|---|---|---|
|
|
10
|
-
| 각 사용자 | Credential + Connection | 개인 Notion OAuth, 개인 API token |
|
|
11
|
-
| Developer 조직 | Cloud Secret | 서버 간 API key, OAuth client secret |
|
|
12
|
-
| 공개 설정 | Connector 또는 manifest | 공개 HTTPS endpoint, provider ID |
|
|
13
|
-
|
|
14
|
-
사용자 token을 Cloud Secret으로 공유하거나 조직 secret을 Instance settings에 복사하지 않습니다.
|
|
15
|
-
|
|
16
|
-
## Manifest 선언
|
|
17
|
-
|
|
18
|
-
OAuth 계정:
|
|
19
|
-
|
|
20
|
-
```json
|
|
21
|
-
{
|
|
22
|
-
"credentials": [
|
|
23
|
-
{
|
|
24
|
-
"id": "notion_account",
|
|
25
|
-
"label": "Notion 계정",
|
|
26
|
-
"description": "공유한 페이지를 읽습니다.",
|
|
27
|
-
"kind": "oauth_access_token",
|
|
28
|
-
"required": true,
|
|
29
|
-
"oauth_provider": "notion",
|
|
30
|
-
"allow_multiple": true
|
|
31
|
-
}
|
|
32
|
-
],
|
|
33
|
-
"connectors": [
|
|
34
|
-
{
|
|
35
|
-
"id": "notion_cloud",
|
|
36
|
-
"kind": "oauth",
|
|
37
|
-
"label": "Notion Cloud",
|
|
38
|
-
"description": "Notion 공식 OAuth 연결",
|
|
39
|
-
"credential_id": "notion_account",
|
|
40
|
-
"cloud_connection_id": "notion"
|
|
41
|
-
}
|
|
42
|
-
]
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
조직 Secret:
|
|
47
|
-
|
|
48
|
-
```json
|
|
49
|
-
{
|
|
50
|
-
"cloud_project_id": "00000000-0000-4000-8000-000000000000",
|
|
51
|
-
"required_secrets": [
|
|
52
|
-
{
|
|
53
|
-
"id": "EXAMPLE_API_KEY",
|
|
54
|
-
"label": "Example API key",
|
|
55
|
-
"description": "서버 간 API 호출에 사용합니다.",
|
|
56
|
-
"required": true
|
|
57
|
-
}
|
|
58
|
-
],
|
|
59
|
-
"connectors": [
|
|
60
|
-
{
|
|
61
|
-
"id": "example_api",
|
|
62
|
-
"kind": "api_key",
|
|
63
|
-
"label": "Example API",
|
|
64
|
-
"description": "조직 API 연결",
|
|
65
|
-
"cloud_secret_id": "EXAMPLE_API_KEY"
|
|
66
|
-
}
|
|
67
|
-
]
|
|
68
|
-
}
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
`cloud_project_id`는 Developer Project에 연결할 때 발급된 실제 UUID를 사용합니다.
|
|
72
|
-
|
|
73
|
-
## Developer Platform 설정
|
|
74
|
-
|
|
75
|
-
1. 조직을 선택하고 Plugin Project를 엽니다.
|
|
76
|
-
2. Secrets에서 manifest의 `required_secrets.id`와 같은 ID를 등록합니다.
|
|
77
|
-
3. Connections에서 `cloud_connection_id`와 같은 provider 설정을 만듭니다.
|
|
78
|
-
4. OAuth authorize URL, token URL, client ID, client secret, redirect URI를 공급자 문서와 맞춥니다.
|
|
79
|
-
5. 필요한 header/content type과 PKCE 지원 여부를 명시합니다.
|
|
80
|
-
6. source를 sync하고 새 Deployment를 build합니다.
|
|
81
|
-
|
|
82
|
-
Secret 값은 저장 후 다시 평문으로 표시되거나 source ZIP, MCP 응답, build log에 포함되면 안 됩니다.
|
|
83
|
-
변경은 새 값으로 덮어쓰고 필요한 경우 provider에서 기존 값을 폐기합니다.
|
|
84
|
-
|
|
85
|
-
## 사용자 연결 흐름
|
|
86
|
-
|
|
87
|
-
```text
|
|
88
|
-
설치·활성화
|
|
89
|
-
→ 필요한 Connection 없음
|
|
90
|
-
→ 설정에서 “계정 연결”
|
|
91
|
-
→ Host가 state와 redirect를 생성
|
|
92
|
-
→ provider 승인
|
|
93
|
-
→ Host callback에서 state 검증과 token 교환
|
|
94
|
-
→ 암호화 저장
|
|
95
|
-
→ default Instance에 Connection 연결
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
`allow_multiple: true`이면 같은 default Instance 안에 여러 Connection을 만들고 사용자가 표시 이름으로
|
|
99
|
-
선택합니다. 같은 package root Instance를 복제해 계정을 추가하지 않습니다.
|
|
100
|
-
|
|
101
|
-
OAuth state와 redirect URI는 필수입니다. PKCE는 공급자 호환 설정에 따라 사용하지만 state 검증을
|
|
102
|
-
대체하지 않습니다. callback이 성공하기 전에 연결 완료로 표시하지 않습니다.
|
|
103
|
-
|
|
104
|
-
## 실행과 갱신
|
|
105
|
-
|
|
106
|
-
Capability는 `connector_id`만 참조합니다. Host는 선택된 Connection의 token을 요청 직전에 주입하고
|
|
107
|
-
응답과 로그에서 제거합니다. credential 거부가 명확하고 provider가 refresh를 지원하면 한 번 갱신한
|
|
108
|
-
뒤 전체 요청을 재시도합니다. 반복 실패는 “다시 연결” 상태로 전환합니다.
|
|
109
|
-
|
|
110
|
-
Connection 상태를 구분합니다.
|
|
111
|
-
|
|
112
|
-
- `ready`: 필요한 credential과 runtime이 준비됨
|
|
113
|
-
- `not_configured`: 필수 Connection/Secret이 없음
|
|
114
|
-
- `unavailable`: provider, 암호화, dependency, network runtime을 사용할 수 없음
|
|
115
|
-
- 만료·거부: 사용자 재연결 필요
|
|
116
|
-
|
|
117
|
-
## 연결 해제와 삭제
|
|
118
|
-
|
|
119
|
-
Instance Connection 삭제는 해당 연결 row의 encrypted credential, 진행 중 OAuth state, 관련 pending
|
|
120
|
-
Host action을 로컬에서 정리하고 generation을 갱신합니다. 이 API를 provider token revoke가
|
|
121
|
-
완료됐다는 증거로 사용하지 않습니다.
|
|
122
|
-
|
|
123
|
-
기존 default credential 해제 API는 provider에 revocation endpoint가 있으면 먼저 revoke하고 성공한
|
|
124
|
-
경우에만 local credential을 삭제합니다. provider 오류가 나면 암호화된 credential을 유지하고 오류를
|
|
125
|
-
반환하므로 사용자가 다시 시도할 수 있습니다. Plugin 전체 삭제는 remote revoke를 가능한 범위에서
|
|
126
|
-
시도한 뒤 `data_policy: retain`이어도 local credential과 OAuth state를 보존하지 않습니다.
|
|
127
|
-
|
|
128
|
-
## 운영 점검
|
|
129
|
-
|
|
130
|
-
- authorize 요청마다 새로운 state가 생성되는가
|
|
131
|
-
- redirect URI가 등록값과 정확히 일치하는가
|
|
132
|
-
- token URL과 content type이 provider 요구와 맞는가
|
|
133
|
-
- client secret과 access token이 로그·MCP·artifact에 없는가
|
|
134
|
-
- 여러 Connection의 token과 표시 이름이 섞이지 않는가
|
|
135
|
-
- token 만료 후 한 번 갱신하고, 실패 시 재연결을 안내하는가
|
|
136
|
-
- Connection 삭제 후 해당 연결의 실행이 즉시 차단되고 local credential이 제거되는가
|
|
137
|
-
- provider revoke가 제품 요구사항이면 공급자 console에서도 token 폐기를 별도로 확인했는가
|
|
138
|
-
- 플러그인 삭제 후 local credential과 OAuth state가 남지 않는가
|
|
139
|
-
|
|
140
|
-
Notion의 실제 구성은 [Notion 플러그인](examples-notion.md)을 참고하세요.
|