@morit/cli 1.4.0 → 1.5.0
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 +4 -2
- package/bin/morit.js +0 -0
- package/package.json +1 -1
- package/src/cli.js +5 -0
- package/src/workspace.js +83 -12
- package/assets/docs/README.md +0 -107
- package/assets/docs/ai-response-and-timeline.md +0 -211
- 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 -225
- 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 -247
- package/assets/docs/packaging-and-testing.md +0 -131
- 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 -188
- package/assets/docs/troubleshooting.md +0 -121
- package/assets/docs/ui-extensions.md +0 -75
- package/assets/docs/ui-runtime-v2.md +0 -406
- package/assets/docs/verification.md +0 -133
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
# 수명주기와 HTTP API
|
|
2
|
-
|
|
3
|
-
모든 사용자 API는 Morit bearer session과 사용자 소유권 검사를 요구합니다. 앱은 repository 계층을
|
|
4
|
-
통해 호출하고 UI에서 URL을 직접 조합하지 않습니다.
|
|
5
|
-
|
|
6
|
-
## 설치 수명주기
|
|
7
|
-
|
|
8
|
-
```text
|
|
9
|
-
package 수신
|
|
10
|
-
→ 크기·ZIP 경로·entry 검사
|
|
11
|
-
→ manifest·fragment compiled 결과 검사
|
|
12
|
-
→ Ed25519 signature와 publisher 확인
|
|
13
|
-
→ dependency bundle 확인
|
|
14
|
-
→ 임시 package 저장
|
|
15
|
-
→ Installation + default Instance + child dependency를 한 transaction으로 기록
|
|
16
|
-
→ package를 content-addressed store에 확정
|
|
17
|
-
→ catalog 새로고침
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
중간 단계가 실패하면 임시 파일과 DB 변경을 함께 rollback합니다. 목록 조회 실패를 설치 성공으로
|
|
21
|
-
바꾸거나, 부분 Installation이 남은 상태에서 duplicate 오류를 반환하지 않습니다.
|
|
22
|
-
|
|
23
|
-
한 Installation에는 설치와 함께 유지되는 default Instance가 있습니다. 여러 외부 계정은 이
|
|
24
|
-
Instance의 여러 Connection으로 관리합니다. 앱이 같은 package의 root Instance를 복제하지 않으며
|
|
25
|
-
dependency Child Instance는 Host가 내부에서 생성·정리합니다.
|
|
26
|
-
|
|
27
|
-
## Marketplace와 설치
|
|
28
|
-
|
|
29
|
-
| method | path | 목적 |
|
|
30
|
-
|---|---|---|
|
|
31
|
-
| GET | `/v1/plugin-marketplace` | 공개 Deployment 목록·검색 |
|
|
32
|
-
| GET | `/v1/plugin-marketplace/{deployment_id}` | 상세 metadata |
|
|
33
|
-
| GET | `/v1/plugin-marketplace/{deployment_id}/icon` | 검증된 icon |
|
|
34
|
-
| POST | `/v1/plugin-marketplace/{deployment_id}/install` | exact Deployment artifact 설치 |
|
|
35
|
-
| POST | `/v1/plugins/install` | raw signed `.mplg` 설치 |
|
|
36
|
-
| POST | `/v1/plugins/create` | 제한된 앱 Builder package 생성·설치 |
|
|
37
|
-
|
|
38
|
-
Raw install content type은 `application/vnd.morit.plugin+zip`, 최대 2 MiB입니다. Marketplace install과
|
|
39
|
-
파일 install은 같은 Host 설치 경로와 rollback 정책을 사용합니다.
|
|
40
|
-
|
|
41
|
-
## 조회와 설정
|
|
42
|
-
|
|
43
|
-
| method | path | 목적 |
|
|
44
|
-
|---|---|---|
|
|
45
|
-
| GET | `/v1/plugins` | 기존 호환용 설치 목록 |
|
|
46
|
-
| GET | `/v1/plugin-installations` | Installation 목록 |
|
|
47
|
-
| GET | `/v1/plugin-instances` | default/child Instance 목록 |
|
|
48
|
-
| GET | `/v1/plugin-instances/{instance_id}` | Instance 상태·Connection·dependency |
|
|
49
|
-
| PATCH | `/v1/plugin-instances/{instance_id}` | 이름, enabled, permission, settings 변경 |
|
|
50
|
-
| DELETE | `/v1/plugin-instances/{instance_id}/storage` | 해당 Instance의 Local Storage 초기화 |
|
|
51
|
-
| DELETE | `/v1/plugin-instances/{instance_id}` | Instance와 descendant 정리용 API |
|
|
52
|
-
| GET | `/v1/plugins/{plugin_id}` | plugin ID 기준 설치 상세 |
|
|
53
|
-
| PATCH | `/v1/plugins/{plugin_id}` | 호환용 default 설치 설정 |
|
|
54
|
-
|
|
55
|
-
새 앱 흐름은 Installation 목록과 default Instance를 사용합니다. enabled, compatibility, granted
|
|
56
|
-
permission, required Connection, dependency가 모두 준비되어야 state가 `active`가 됩니다. 설정 변경은
|
|
57
|
-
generation을 갱신해 오래된 실행 결과가 새 상태를 덮지 못하게 합니다.
|
|
58
|
-
|
|
59
|
-
## Connection과 OAuth
|
|
60
|
-
|
|
61
|
-
| method | path | 목적 |
|
|
62
|
-
|---|---|---|
|
|
63
|
-
| POST | `/v1/plugin-instances/{instance_id}/connections` | Credential Connection 생성 |
|
|
64
|
-
| PATCH | `/v1/plugin-instances/{instance_id}/connections/{connection_id}` | label·default 선택 변경 |
|
|
65
|
-
| POST | `/v1/plugin-instances/{instance_id}/connections/{connection_id}/oauth/start` | Instance OAuth 시작 |
|
|
66
|
-
| DELETE | `/v1/plugin-instances/{instance_id}/connections/{connection_id}` | Connection 해제 |
|
|
67
|
-
| PUT | `/v1/plugins/{plugin_id}/credentials/{credential_id}` | legacy/default API token 설정 |
|
|
68
|
-
| POST | `/v1/plugins/{plugin_id}/credentials/{credential_id}/oauth/start` | legacy/default OAuth 시작 |
|
|
69
|
-
| GET | `/v1/plugins/oauth/callback/{provider_id}` | provider callback |
|
|
70
|
-
| DELETE | `/v1/plugins/{plugin_id}/credentials/{credential_id}` | legacy/default credential 삭제 |
|
|
71
|
-
|
|
72
|
-
`allow_multiple: true`인 Credential은 Connection을 여러 개 만들 수 있습니다. OAuth callback은 state와
|
|
73
|
-
code를 확인한 뒤 Connection을 갱신하며 token을 URL·로그·응답에 반환하지 않습니다.
|
|
74
|
-
|
|
75
|
-
## Capability 실행
|
|
76
|
-
|
|
77
|
-
| method | path | 목적 |
|
|
78
|
-
|---|---|---|
|
|
79
|
-
| POST | `/v1/plugin-instances/{instance_id}/capabilities/{capability}/execute` | 특정 Instance 실행 |
|
|
80
|
-
| POST | `/v1/plugins/{plugin_id}/capabilities/{capability}/execute` | default Instance 호환 실행 |
|
|
81
|
-
| POST | `/v1/plugins/search` | enabled Search Provider 통합 검색 |
|
|
82
|
-
| POST | `/v1/plugins/background/run` | due background 실행 |
|
|
83
|
-
|
|
84
|
-
실행 응답:
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"plugin_id": "com.example.study",
|
|
89
|
-
"instance_id": "00000000-0000-4000-8000-000000000000",
|
|
90
|
-
"capability": "com.example.study.today",
|
|
91
|
-
"state": "completed",
|
|
92
|
-
"summary": "오늘 일정은 3개입니다.",
|
|
93
|
-
"data": {},
|
|
94
|
-
"evidence": [],
|
|
95
|
-
"error_message": null,
|
|
96
|
-
"error_code": null,
|
|
97
|
-
"retryable": false
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Host는 실행 직전에 actor, owner, enabled, compatible version, generation, permission, Connection,
|
|
102
|
-
dependency, timeout을 다시 확인합니다. 실패는 `error_code`와 `retryable`을 구분하며 성공 summary로
|
|
103
|
-
숨기지 않습니다.
|
|
104
|
-
|
|
105
|
-
## Host action outbox
|
|
106
|
-
|
|
107
|
-
| method | path | 목적 |
|
|
108
|
-
|---|---|---|
|
|
109
|
-
| GET | `/v1/plugins/host-actions` | 최대 100개의 pending action lease |
|
|
110
|
-
| POST | `/v1/plugins/host-actions/{event_id}/result` | 플랫폼 적용 성공·실패 ACK |
|
|
111
|
-
|
|
112
|
-
앱은 action을 적용한 뒤 반드시 result를 전송합니다. lease 만료 시 중복 전달될 수 있으므로
|
|
113
|
-
notification key와 storage action은 idempotent하게 처리합니다.
|
|
114
|
-
|
|
115
|
-
## Package, UI 이미지와 삭제
|
|
116
|
-
|
|
117
|
-
| method | path | 목적 |
|
|
118
|
-
|---|---|---|
|
|
119
|
-
| GET | `/v1/plugins/{plugin_id}/package` | 설치된 exact `.mplg` attachment 다운로드 |
|
|
120
|
-
| GET | `/v1/plugins/{plugin_id}/assets/{asset_path:path}` | 검증된 package asset 읽기 |
|
|
121
|
-
| POST | `/v1/plugin-instances/{instance_id}/ui-images:fetch` | capability 결과의 원격 이미지 Host proxy |
|
|
122
|
-
| DELETE | `/v1/plugins/{plugin_id}` | Installation, Instance, Connection 삭제 |
|
|
123
|
-
|
|
124
|
-
package 응답은 attachment `Content-Disposition`을 사용합니다. 삭제 시 `purge_data`와 manifest
|
|
125
|
-
`data_policy`를 적용하되 credential, OAuth state, pending Host action은 항상 별도 보안 정리를 거칩니다.
|
|
126
|
-
|
|
127
|
-
Storage 초기화는 actor가 소유한 정확한 Instance만 대상으로 하며 같은 Installation의 다른 Instance와
|
|
128
|
-
다른 사용자 데이터는 건드리지 않습니다. `data_policy: retain`은 uninstall/reinstall에만 적용되고
|
|
129
|
-
사용자가 명시적으로 초기화하면 즉시 삭제됩니다. 자세한 수명주기는
|
|
130
|
-
[Plugin Local Storage와 AI 접근](plugin-storage.md)을 참고하세요.
|
|
131
|
-
|
|
132
|
-
Asset API는 활성화된 사용자 소유 설치가 UI Runtime v2에서 실제 참조한 이미지에만 접근할 수
|
|
133
|
-
있습니다. Host는 저장 package의 hash·signature·manifest를 다시 확인하고 확장자와 PNG/JPEG/GIF/WebP
|
|
134
|
-
magic byte, 실제 이미지 포맷을 검사합니다. package asset과 원격 이미지에 공통으로 가로·세로 각각
|
|
135
|
-
4096 px, 애니메이션 128 frame, `width × height × frame 수` 기준 frame 합산 16,000,000 pixel
|
|
136
|
-
한도를 적용합니다. 응답은 private cache, `ETag`, `nosniff`, same-origin resource policy를 사용하며
|
|
137
|
-
traversal·미참조 asset·다른 사용자의 설치는 노출하지 않습니다.
|
|
138
|
-
|
|
139
|
-
원격 이미지 API는 앱의 Host renderer가 호출하는 인증된 binary endpoint입니다. JSON body는
|
|
140
|
-
`{"url":"https://..."}`이고, URL은 `image` 또는 `avatar`의 capability 결과 binding에서
|
|
141
|
-
해석한 값이어야 합니다. 앱과 플러그인은 외부 URL을 직접 fetch하지 않습니다. 요청 actor가 Instance
|
|
142
|
-
owner여야 하고, package Manifest의 `network` 요청과 해당 활성 Instance의 `network` grant가 모두
|
|
143
|
-
유효해야 합니다.
|
|
144
|
-
|
|
145
|
-
Host는 공개 HTTPS만 허용하고 DNS/IP SSRF 검사를 거치며 redirect를 따르지 않습니다. upstream
|
|
146
|
-
응답은 PNG/JPEG/GIF/WebP MIME type, magic byte, 실제 이미지 포맷이 일치해야 합니다. 본문은 최대
|
|
147
|
-
512 KiB이고 위의 공통 dimension·animation 한도를 적용합니다. 성공 응답은 `private, no-store`,
|
|
148
|
-
`Vary: Authorization`, `nosniff`, same-origin resource policy를 사용합니다.
|
|
149
|
-
권한 부족은 `403`, 비활성화·generation/grant 변경은 `409`, unsafe URL·redirect는 `422`, 크기 초과는
|
|
150
|
-
`413`, 지원하지 않거나 손상된 이미지는 `415`, upstream 실패는 `502`, Host network 경계 미설정은
|
|
151
|
-
`503`으로 구분합니다. 소유하지 않은 Instance는 상세 정보를 노출하지 않습니다.
|
|
152
|
-
|
|
153
|
-
AI 생성/분석 artifact는 별도 소유권 경계인
|
|
154
|
-
`GET /v1/ai/conversations/{conversation_id}/code-executions/{execution_id}/artifacts/{attachment_id}/access`
|
|
155
|
-
에서만 signed URL을 발급합니다. 완료된 `python_code`, `image_generation`, `image_edit` 실행의 저장된
|
|
156
|
-
artifact manifest와 attachment 행이 모두 일치해야 하며 다른 대화·실행의 attachment ID는 404입니다.
|
|
157
|
-
|
|
158
|
-
## 앱 시작 복구
|
|
159
|
-
|
|
160
|
-
시작할 때 DB Installation과 content-addressed package store를 함께 점검합니다.
|
|
161
|
-
|
|
162
|
-
- DB가 없는 고아 임시/package 파일 정리
|
|
163
|
-
- DB가 가리키는 package가 없거나 hash가 다르면 명확한 load error
|
|
164
|
-
- 오래된 partial Connection/OAuth state 만료
|
|
165
|
-
- dependency Child Instance 재조정
|
|
166
|
-
- pending Host action lease 회수
|
|
167
|
-
|
|
168
|
-
손상된 metadata를 빈 목록으로 바꾸지 않습니다. 목록 없음, 인증 실패, 저장소 손상, network 실패를
|
|
169
|
-
각각 구분해 사용자에게 재시도 또는 복구 동작을 제공합니다.
|
package/assets/docs/local-cli.md
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
# 공식 CLI 개발 흐름
|
|
2
|
-
|
|
3
|
-
`@morit/cli`는 로컬 source 작성, Cloud Project 연결, signed build와 Deployment를 제공합니다.
|
|
4
|
-
|
|
5
|
-
## 설치 없이 실행
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npx -y @morit/cli --help
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Node.js 20.12 이상을 사용합니다. 자동화에서는 각 명령에 `--json`을 추가해 구조화된 출력을 받을
|
|
12
|
-
수 있습니다.
|
|
13
|
-
|
|
14
|
-
## 1. Setup
|
|
15
|
-
|
|
16
|
-
```bash
|
|
17
|
-
npx -y @morit/cli plugin setup . \
|
|
18
|
-
--id com.example.study \
|
|
19
|
-
--name "Study" \
|
|
20
|
-
--publisher example
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
기존 유효 프로젝트에서는 ID 옵션 없이 실행할 수 있습니다. `morit-plugin.json`은 Project ID,
|
|
24
|
-
organization ID, revision, source digest, API URL만 저장합니다.
|
|
25
|
-
|
|
26
|
-
## 2. Validate와 build
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npx -y @morit/cli plugin validate .
|
|
30
|
-
npx -y @morit/cli plugin build . --output ./dist/study-1.0.0.mplg
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
build는 fragment를 compiled manifest로 합치고 source·asset·dependency 참조를 확인한 뒤 publisher별
|
|
34
|
-
Ed25519 key로 자동 서명합니다. key는 기본적으로 사용자 홈의 `.morit/plugin-mcp/keys` 아래에
|
|
35
|
-
보호해 저장하고 source나 `.mplg`에 private key를 넣지 않습니다. build 결과를 같은 과정에서 다시
|
|
36
|
-
열어 signature와 package 구조를 확인합니다.
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npx -y @morit/cli plugin preview . --output ./dist/preview.html
|
|
40
|
-
python tools/morit_plugin.py preview . --output ./dist/preview.html
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
두 명령과 Local/Remote MCP의 `morit_project_preview`는 같은 검증 계약을 사용합니다. 결과는 script를
|
|
44
|
-
실행하지 않는 safe HTML이며 light/dark 토글을 포함합니다. 실제 Flutter route·focus·Tool 실행은
|
|
45
|
-
서명 package를 앱에 설치해 확인합니다.
|
|
46
|
-
|
|
47
|
-
## 3. 로그인
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npx -y @morit/cli login
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
CLI가 브라우저 device flow를 열고 사용자가 표시된 코드를 승인합니다. Windows는 DPAPI, macOS는
|
|
54
|
-
Keychain, Linux는 Secret Service의 `secret-tool`을 사용합니다. CI는 세션 환경변수
|
|
55
|
-
`MORIT_ACCESS_TOKEN`을 사용할 수 있지만 source, 로그, artifact에 기록하지 않습니다.
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
npx -y @morit/cli logout
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## 4. Cloud Project 연결
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
npx -y @morit/cli plugin add .
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
현재 조직에 Project를 만들고 local source를 연결합니다. 이미 연결된 프로젝트에서 다시 `add`하지
|
|
68
|
-
않습니다.
|
|
69
|
-
|
|
70
|
-
## 5. Sync
|
|
71
|
-
|
|
72
|
-
로컬 → Cloud:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
npx -y @morit/cli plugin sync .
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Cloud → 로컬:
|
|
79
|
-
|
|
80
|
-
```bash
|
|
81
|
-
npx -y @morit/cli plugin sync . --pull
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
마지막 sync 이후 로컬 변경이 있으면 pull이 중단됩니다. Cloud 사본으로 덮어쓸 의도가 명확할 때만
|
|
85
|
-
`--pull --force`를 사용합니다. revision 충돌은 새 파일을 읽고 변경을 합친 뒤 다시 push합니다.
|
|
86
|
-
|
|
87
|
-
## 6. Deploy
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
npx -y @morit/cli plugin deploy . --visibility private
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
명령은 최신 source push → local signed build → exact artifact upload → immutable Deployment 생성을
|
|
94
|
-
수행합니다. 응답의 `deployment_id`, `local_artifact_path`, SHA-256, visibility를 기록합니다. 처음에는
|
|
95
|
-
private로 실사용 테스트한 뒤 Marketplace metadata와 공개 준비가 끝났을 때 public으로 변경합니다.
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
npx -y @morit/cli deployment publish <deployment-id> --visibility public
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Cloud와 로컬에서 같은 이름의 package를 다시 만들지 말고 Deployment가 가진 exact artifact hash를
|
|
102
|
-
설치 경로 전체에서 유지합니다.
|
|
103
|
-
|
|
104
|
-
## 조회와 삭제
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
npx -y @morit/cli project list
|
|
108
|
-
npx -y @morit/cli project get <project-id>
|
|
109
|
-
npx -y @morit/cli deployment list <project-id>
|
|
110
|
-
npx -y @morit/cli deployment get <deployment-id>
|
|
111
|
-
npx -y @morit/cli project delete <project-id>
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
활성 Deployment가 있는 Project 삭제는 서버 정책에 따라 거부될 수 있습니다. 사용자 설치를
|
|
115
|
-
Deployment 정리로 우회 삭제하지 않습니다.
|
|
116
|
-
|
|
117
|
-
## CI 예
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
npx -y @morit/cli plugin validate . --json
|
|
121
|
-
npx -y @morit/cli plugin build . --output ./dist/plugin.mplg --json
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
배포 CI에는 최소 scope의 짧은 수명 token을 주입하고 작업이 끝나면 폐기합니다. build artifact의
|
|
125
|
-
SHA-256과 Deployment의 SHA-256을 비교합니다.
|
package/assets/docs/manifest.md
DELETED
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
# Manifest 레퍼런스
|
|
2
|
-
|
|
3
|
-
`manifest.json`은 UTF-8 JSON 객체입니다. Host는 schema 1과 2를 읽으며 새 프로젝트는 schema 2를
|
|
4
|
-
사용합니다. 알 수 없는 필드는 무시하지 않고 오류로 처리합니다.
|
|
5
|
-
|
|
6
|
-
## 전체 형태
|
|
7
|
-
|
|
8
|
-
```json
|
|
9
|
-
{
|
|
10
|
-
"schema_version": 2,
|
|
11
|
-
"id": "com.example.notes",
|
|
12
|
-
"name": "Notes",
|
|
13
|
-
"icon": "assets/icon.png",
|
|
14
|
-
"short_description": "연결된 노트를 검색합니다.",
|
|
15
|
-
"description": "내 노트 계정을 연결해 검색하고 요약합니다.",
|
|
16
|
-
"category": "productivity",
|
|
17
|
-
"keywords": ["notes", "search"],
|
|
18
|
-
"developer": {"name": "Example", "url": "https://example.com"},
|
|
19
|
-
"homepage_url": "https://example.com/notes",
|
|
20
|
-
"privacy_policy_url": "https://example.com/privacy",
|
|
21
|
-
"publisher": "example",
|
|
22
|
-
"version": "1.0.0",
|
|
23
|
-
"min_morit_version": "1.7.12",
|
|
24
|
-
"max_morit_version": "1.999.999",
|
|
25
|
-
"permissions": ["network", "credentials"],
|
|
26
|
-
"capabilities": [
|
|
27
|
-
{
|
|
28
|
-
"id": "com.example.notes.echo",
|
|
29
|
-
"kind": "tool",
|
|
30
|
-
"title": "입력 확인",
|
|
31
|
-
"description": "입력한 검색어를 확인합니다.",
|
|
32
|
-
"permissions": [],
|
|
33
|
-
"runtime": {"adapter": "text_template", "template": "검색어: {query}"}
|
|
34
|
-
}
|
|
35
|
-
],
|
|
36
|
-
"ui_extensions": [],
|
|
37
|
-
"credentials": [],
|
|
38
|
-
"slash_commands": [],
|
|
39
|
-
"connectors": [],
|
|
40
|
-
"dependencies": [],
|
|
41
|
-
"required_secrets": [],
|
|
42
|
-
"data_policy": "purge"
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
`icon`을 선언했다면 `assets/icon.png` 파일도 실제 프로젝트에 있어야 합니다. 준비 전에는 필드를
|
|
47
|
-
생략하고 존재하지 않는 asset 경로를 넣지 않습니다.
|
|
48
|
-
|
|
49
|
-
## 신원과 표시 정보
|
|
50
|
-
|
|
51
|
-
| 필드 | 규칙 |
|
|
52
|
-
|---|---|
|
|
53
|
-
| `id` | 소문자 reverse-domain ID, 예: `com.example.notes` |
|
|
54
|
-
| `name` | 1~80자 |
|
|
55
|
-
| `publisher` | 2~120자의 영문·숫자·점·밑줄·하이픈 |
|
|
56
|
-
| `version` | semantic version |
|
|
57
|
-
| `min_morit_version`, `max_morit_version` | semantic version, 둘 다 필수 |
|
|
58
|
-
| `icon` | 패키지의 PNG/JPEG/WebP `assets/` 경로 |
|
|
59
|
-
| `short_description` | 최대 160자 |
|
|
60
|
-
| `description` | 최대 4,000자 |
|
|
61
|
-
| `category` | 소문자로 시작하는 2~40자 ID |
|
|
62
|
-
| `keywords` | 최대 12개의 고유한 1~40자 문자열 |
|
|
63
|
-
| URL 필드 | 사용자 정보가 없는 공개 HTTPS URL |
|
|
64
|
-
|
|
65
|
-
`cloud_project_id`는 Cloud 기능을 쓸 때 Developer Project가 채우는 UUID입니다. 프로젝트 source에
|
|
66
|
-
직접 추측해 넣지 않습니다. `required_secrets` 또는 `cloud_connection_id`를 사용하는 Connector가
|
|
67
|
-
있으면 이 값이 필요합니다.
|
|
68
|
-
|
|
69
|
-
## 권한과 데이터 정책
|
|
70
|
-
|
|
71
|
-
Manifest `permissions`는 플러그인이 요청할 수 있는 최대 범위입니다. 각 capability와 UI extension은
|
|
72
|
-
그중 자신에게 필요한 일부만 다시 선언합니다.
|
|
73
|
-
|
|
74
|
-
`data_policy`는 삭제 시 플러그인 storage 처리 방식을 정합니다.
|
|
75
|
-
|
|
76
|
-
- `purge`: 설치와 함께 저장한 플러그인 데이터를 삭제
|
|
77
|
-
- `retain`: 재설치를 위해 일반 storage를 유지
|
|
78
|
-
|
|
79
|
-
credential, OAuth state, pending notification은 두 정책과 무관하게 보안 수명주기에 따라 정리됩니다.
|
|
80
|
-
|
|
81
|
-
### Local Storage
|
|
82
|
-
|
|
83
|
-
새 저장소는 schema 2에서 명시적으로 선언합니다.
|
|
84
|
-
|
|
85
|
-
```json
|
|
86
|
-
{
|
|
87
|
-
"permissions": ["storage", "ai_storage"],
|
|
88
|
-
"storage": {
|
|
89
|
-
"version": 1,
|
|
90
|
-
"namespaces": [
|
|
91
|
-
{"id": "default", "max_bytes": 131072, "ai_access": "read"},
|
|
92
|
-
{"id": "tasks", "max_bytes": 262144, "ai_access": "read_write"}
|
|
93
|
-
],
|
|
94
|
-
"migrations": []
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
`ai_access`가 `read` 또는 `read_write`인 namespace가 있으면 `ai_storage` 권한이 필수입니다.
|
|
100
|
-
namespace, quota, migration과 표준 CRUD Tool은 [Plugin Local Storage와 AI 접근](plugin-storage.md)을
|
|
101
|
-
참고하세요.
|
|
102
|
-
|
|
103
|
-
## Capability
|
|
104
|
-
|
|
105
|
-
```json
|
|
106
|
-
{
|
|
107
|
-
"id": "com.example.notes.search",
|
|
108
|
-
"kind": "tool",
|
|
109
|
-
"title": "노트 검색",
|
|
110
|
-
"description": "연결한 계정의 노트를 검색합니다.",
|
|
111
|
-
"permissions": ["network", "credentials"],
|
|
112
|
-
"timeout_seconds": 10,
|
|
113
|
-
"input_schema": {
|
|
114
|
-
"type": "object",
|
|
115
|
-
"properties": {"query": {"type": "string", "minLength": 1}},
|
|
116
|
-
"required": ["query"]
|
|
117
|
-
},
|
|
118
|
-
"output_schema": {
|
|
119
|
-
"type": "object",
|
|
120
|
-
"properties": {"items": {"type": "array", "items": {"type": "object"}}},
|
|
121
|
-
"required": ["items"]
|
|
122
|
-
},
|
|
123
|
-
"runtime": {
|
|
124
|
-
"adapter": "http_json",
|
|
125
|
-
"connector_id": "notes_api"
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
`kind`는 `tool`, `skill`, `provider`, `background`, `notification` 중 하나입니다. 최대 64개이며
|
|
131
|
-
timeout은 0.1~30초입니다. capability 권한은 Manifest 권한의 부분집합이어야 합니다. `tool`과
|
|
132
|
-
`skill`은 실행 가능한 runtime adapter가 필수입니다. Search `provider`는
|
|
133
|
-
`runtime.role: "search"`와 adapter가 필요합니다.
|
|
134
|
-
`input_schema`/`output_schema`는 선택이지만 선언하면 AI Tool·UI·Host가 모두 같은
|
|
135
|
-
안전한 JSON Schema 부분집합으로 입력과 결과를 검증합니다. UI binding 정적 검사는
|
|
136
|
-
[UI Runtime v2](ui-runtime-v2.md)를 참고하세요.
|
|
137
|
-
|
|
138
|
-
## UI extension
|
|
139
|
-
|
|
140
|
-
```json
|
|
141
|
-
{
|
|
142
|
-
"id": "com.example.notes.screen",
|
|
143
|
-
"point": "screen",
|
|
144
|
-
"title": "내 노트",
|
|
145
|
-
"order": 10,
|
|
146
|
-
"permissions": ["network", "credentials"],
|
|
147
|
-
"config": {
|
|
148
|
-
"ui_schema": 2,
|
|
149
|
-
"view": {"type": "text", "props": {"text": "노트를 검색하세요."}}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
최대 64개입니다. `point`는 `screen`, `surface`, `menu`, `action`, `card`, `settings`,
|
|
155
|
-
`workspace`, `response` 중 하나입니다. 상세 선택 기준은 [UI extension point](ui-extensions.md),
|
|
156
|
-
config 계약은 [UI Runtime v2](ui-runtime-v2.md)를 참고하세요.
|
|
157
|
-
|
|
158
|
-
`point: "response"`는 `ui_schema: 2`와 A2UI v0.9 metadata/schema가 필수이며 이전 Response UI
|
|
159
|
-
형식은 허용하지 않습니다. Host가 활성 Instance, 권한, data source와 schema를 확인한 뒤 catalog에
|
|
160
|
-
동적 등록합니다. AI는 등록된 컴포넌트 ID만 선택할 수 있고 결과 데이터는 Host가 주입합니다.
|
|
161
|
-
|
|
162
|
-
## Credential과 Connector
|
|
163
|
-
|
|
164
|
-
Credential은 사용자별 비밀 값의 종류를 선언합니다.
|
|
165
|
-
|
|
166
|
-
```json
|
|
167
|
-
{
|
|
168
|
-
"id": "notes_account",
|
|
169
|
-
"label": "Notes 계정",
|
|
170
|
-
"description": "노트를 읽는 OAuth 연결",
|
|
171
|
-
"kind": "oauth_access_token",
|
|
172
|
-
"required": true,
|
|
173
|
-
"oauth_provider": "notion",
|
|
174
|
-
"allow_multiple": true
|
|
175
|
-
}
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
`kind`는 `api_token` 또는 `oauth_access_token`입니다. credential이 하나라도 있으면 Manifest에
|
|
179
|
-
`credentials` 권한이 필요합니다. `allow_multiple`의 기본값은 `true`이며 Instance마다 연결을
|
|
180
|
-
따로 관리합니다.
|
|
181
|
-
|
|
182
|
-
Connector는 endpoint, credential, timeout, retry, rate limit을 capability에서 분리합니다.
|
|
183
|
-
|
|
184
|
-
```json
|
|
185
|
-
{
|
|
186
|
-
"id": "notes_api",
|
|
187
|
-
"kind": "oauth",
|
|
188
|
-
"label": "Notes API",
|
|
189
|
-
"description": "공식 API 연결",
|
|
190
|
-
"credential_id": "notes_account",
|
|
191
|
-
"cloud_connection_id": "notion",
|
|
192
|
-
"timeout_seconds": 10,
|
|
193
|
-
"retry": {"max_attempts": 3},
|
|
194
|
-
"rate_limit": {"requests": 60, "period_seconds": 60}
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
`kind`는 `https`, `oauth`, `api_key`, `mcp`입니다. 공개 endpoint를 직접 지정할 때는 HTTPS만
|
|
199
|
-
허용합니다. timeout은 0.1~30초, retry는 1~4회, rate-limit 기간은 1~3,600초입니다. OAuth는
|
|
200
|
-
credential이 필수이고 API key는 사용자 credential이나 선언된 Cloud secret 중 하나가 필요합니다.
|
|
201
|
-
|
|
202
|
-
Cloud Secret을 쓰려면 먼저 `required_secrets`에 대문자 환경 ID를 선언합니다.
|
|
203
|
-
|
|
204
|
-
```json
|
|
205
|
-
{
|
|
206
|
-
"id": "EXAMPLE_API_KEY",
|
|
207
|
-
"label": "Example API key",
|
|
208
|
-
"description": "서버 간 요청에 사용합니다.",
|
|
209
|
-
"required": true
|
|
210
|
-
}
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
## Dependency와 child package
|
|
214
|
-
|
|
215
|
-
```json
|
|
216
|
-
{
|
|
217
|
-
"id": "notes",
|
|
218
|
-
"plugin_id": "com.example.notes_core",
|
|
219
|
-
"required": true,
|
|
220
|
-
"min_version": "1.0.0",
|
|
221
|
-
"max_version": "1.999.999",
|
|
222
|
-
"package_path": "children/notes-core.mplg",
|
|
223
|
-
"exposed_capabilities": ["com.example.notes_core.search"]
|
|
224
|
-
}
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
`package_path`가 있으면 정확히 대응하는 signed child `.mplg`가 패키지에 있어야 합니다. 부모는
|
|
228
|
-
`child_plugin` adapter와 `exposed_capabilities`에 적힌 capability만 호출할 수 있습니다. 부모와
|
|
229
|
-
자식은 Instance, settings, credential, storage를 공유하지 않습니다.
|
|
230
|
-
|
|
231
|
-
## Slash Command
|
|
232
|
-
|
|
233
|
-
```json
|
|
234
|
-
{
|
|
235
|
-
"id": "com.example.notes.command",
|
|
236
|
-
"command": "notes",
|
|
237
|
-
"title": "노트 검색",
|
|
238
|
-
"description": "연결한 노트를 검색합니다.",
|
|
239
|
-
"capability": "com.example.notes.search",
|
|
240
|
-
"argument_template": {"limit": 10}
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
최대 32개입니다. `command`는 `/` 없이 소문자·숫자로 시작하는 1~32자이며 `_`, `-`를 사용할 수
|
|
245
|
-
있습니다. 대상은 같은 플러그인의 `tool` 또는 `skill`이어야 합니다.
|
|
246
|
-
|
|
247
|
-
다음은 [Instance와 Connector 모델](instances-and-connectors.md)입니다.
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# 패키징과 테스트
|
|
2
|
-
|
|
3
|
-
## `.mplg` 형식
|
|
4
|
-
|
|
5
|
-
`.mplg`는 content type `application/vnd.morit.plugin+zip`인 deterministic ZIP입니다. 최대 2 MiB,
|
|
6
|
-
entry 최대 64개이며 다음 안전 검사를 통과해야 합니다.
|
|
7
|
-
|
|
8
|
-
- 중복 entry 없음
|
|
9
|
-
- 절대 경로, `..`, 역슬래시, symlink 없음
|
|
10
|
-
- 허용된 source·asset·child package만 포함
|
|
11
|
-
- compiled manifest가 package 파일과 일치
|
|
12
|
-
- 모든 entry digest를 포함한 embedded Ed25519 signature 유효
|
|
13
|
-
|
|
14
|
-
정상 signature 검증을 제거하거나 ZIP 검사 오류를 숨겨 설치하지 않습니다.
|
|
15
|
-
|
|
16
|
-
## 기본 자동 서명
|
|
17
|
-
|
|
18
|
-
공식 Node CLI build는 publisher별 key를 안전한 로컬 디렉터리에 만들고 자동 서명한 뒤 결과를 다시
|
|
19
|
-
검증합니다.
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
npx -y @morit/cli plugin build . --output ./dist/plugin.mplg
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
`.mplg`에는 public key와 signature만 들어갑니다. trusted publisher 등록은 선택 사항이지만 등록된
|
|
26
|
-
publisher key가 있으면 embedded key와 일치해야 합니다.
|
|
27
|
-
|
|
28
|
-
저장소 Python pipeline에서 조직의 고정 key를 사용할 때는 다음과 같습니다.
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
python tools/morit_plugin.py package . \
|
|
32
|
-
--private-key /secure/example.ed25519 \
|
|
33
|
-
--output ./dist/plugin.mplg
|
|
34
|
-
python tools/morit_plugin.py verify ./dist/plugin.mplg
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
`build --unsigned`는 별도 signing pipeline의 중간 파일을 만들 때만 사용합니다. unsigned package는
|
|
38
|
-
설치할 수 없으며 사용자에게 최종 `.mplg`로 제공하지 않습니다.
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
python tools/morit_plugin.py build . --unsigned --output ./dist/unsigned.mplg
|
|
42
|
-
python tools/morit_plugin.py sign ./dist/unsigned.mplg \
|
|
43
|
-
--private-key /secure/example.ed25519 \
|
|
44
|
-
--output ./dist/plugin.mplg
|
|
45
|
-
python tools/morit_plugin.py verify ./dist/plugin.mplg
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Source ZIP과 package
|
|
49
|
-
|
|
50
|
-
Source ZIP은 편집 가능한 전체 프로젝트이고 `.mplg`는 설치에 필요한 compiled subset입니다.
|
|
51
|
-
|
|
52
|
-
| 산출물 | 용도 | 비밀 값 |
|
|
53
|
-
|---|---|---|
|
|
54
|
-
| `*-source.zip` | 리뷰·다른 환경에서 편집 | 포함 금지 |
|
|
55
|
-
| `*.mplg` | 앱 설치·Deployment | private key/secret 포함 금지 |
|
|
56
|
-
| `*-preview.html` | 구조 보조 점검 | 실행 코드 포함 금지 |
|
|
57
|
-
|
|
58
|
-
서로 다른 artifact를 같은 확장자나 MIME으로 제공하지 않습니다.
|
|
59
|
-
|
|
60
|
-
## 테스트 층
|
|
61
|
-
|
|
62
|
-
### 1. Source 계약
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
npx -y @morit/cli plugin validate .
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
Manifest 필드, fragment 병합, 권한 부분집합, capability/runtime, UI tree·binding·route, Credential·
|
|
69
|
-
Connector, source entrypoint, dependency를 검사합니다.
|
|
70
|
-
|
|
71
|
-
### 2. Preview
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
npx -y @morit/cli plugin preview . --output ./dist/preview.html
|
|
75
|
-
python tools/morit_plugin.py preview . --output ./dist/preview.html
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
정보 구조와 compiled manifest, light/dark partial theme를 점검합니다. Preview는 Flutter Host와
|
|
79
|
-
같은 state/binding/compound condition, form validation, autocomplete·searchable select, 순차·병렬
|
|
80
|
-
action flow, route parameter/result 규칙을 사용하며 Runtime Inspector로 변경을 확인할 수 있습니다.
|
|
81
|
-
실제 인증 Tool·native route stack·플랫폼 IME 동작은 Flutter 앱에서 최종 확인합니다.
|
|
82
|
-
|
|
83
|
-
### 3. Package reopen
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
npx -y @morit/cli plugin build . --output ./dist/plugin.mplg
|
|
87
|
-
python tools/morit_plugin.py verify ./dist/plugin.mplg
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
크기, SHA-256, entry, manifest, publisher, embedded signature를 확인합니다.
|
|
91
|
-
|
|
92
|
-
### 4. Backend/API
|
|
93
|
-
|
|
94
|
-
테스트 계정으로 설치 → 목록 → 활성화 → permission/settings → Connection → capability → 삭제 순서를
|
|
95
|
-
실행합니다. 위험한 ZIP, 위조 signature, 등록된 publisher key 불일치, 잘못된 manifest를 각각 차단하고
|
|
96
|
-
실패 뒤 부분 DB row와 package file이 남지 않는지 확인합니다.
|
|
97
|
-
|
|
98
|
-
### 5. 앱 실사용
|
|
99
|
-
|
|
100
|
-
- 앱 데이터 삭제 후 첫 설치
|
|
101
|
-
- Marketplace 조회·새로고침·재시도
|
|
102
|
-
- local file 설치
|
|
103
|
-
- 활성화와 권한 허용·거부
|
|
104
|
-
- 화면 loading·empty·error·retry
|
|
105
|
-
- 한국어 IME 입력 직후 Tool arguments, focus 전환과 controller 동기화
|
|
106
|
-
- autocomplete, searchable/async select, form validation, 순차·병렬 action flow
|
|
107
|
-
- route parameters·result 반환, permission/connection/platform/screen 복합 조건
|
|
108
|
-
- 홈·플러그인 화면·AI Response UI 간 source cache 재사용과 storage mutation 자동 refresh
|
|
109
|
-
- A2UI 답변 완료 후 즉시 렌더링, 실패 생략, 로딩 UI 미표시
|
|
110
|
-
- 미디어 overlay의 영상·MP3 옵션, 실제 변환·진행률·재시도·모바일 너비
|
|
111
|
-
- 시스템/라이트/다크 전환과 기존 theme 없는 package 호환
|
|
112
|
-
- Storage CRUD·검색·즉시 UI 갱신, AI read/read_write 권한
|
|
113
|
-
- 사용자·Installation·Instance·namespace 격리와 quota/revision/generation 충돌
|
|
114
|
-
- Storage migration, reset, purge/retain uninstall/reinstall
|
|
115
|
-
- 동일 package 재설치와 version update
|
|
116
|
-
- OAuth 여러 Connection과 재연결
|
|
117
|
-
- notification 실제 표시
|
|
118
|
-
- 삭제와 재설치
|
|
119
|
-
|
|
120
|
-
### 6. 회귀 검사
|
|
121
|
-
|
|
122
|
-
SDK, Python Host, Flutter schema/runtime, Local MCP, Remote MCP, Preview가 같은 contract와 경계를
|
|
123
|
-
반환하고 같은 fixture를 허용·거부하는지 확인합니다. capability input/output schema와 UI
|
|
124
|
-
binding도 모든 validator에서 같이 실패해야 합니다. 문서 예제 JSON도 source 검증 과정에서
|
|
125
|
-
실행해 구현과 함께 변경합니다.
|
|
126
|
-
|
|
127
|
-
## 실패 시 원칙
|
|
128
|
-
|
|
129
|
-
validate, build, upload, DB install 단계 중 하나가 실패하면 성공 메시지나 artifact 링크를 반환하지
|
|
130
|
-
않습니다. 설치는 임시 package 검증 후 DB와 파일을 commit하고, 중간 실패 시 둘 다 정리합니다.
|
|
131
|
-
구체 오류 분류는 [오류 해결](troubleshooting.md)을 참고하세요.
|