@morit/cli 1.0.0 → 1.1.1
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 +53 -18
- package/assets/docs/README.md +105 -0
- package/assets/docs/ai-response-and-timeline.md +125 -0
- package/assets/docs/ai-skill-and-docx-workflow.md +83 -0
- package/assets/docs/app-builder.md +56 -0
- package/assets/docs/authentication.md +140 -0
- package/assets/docs/components.md +159 -0
- package/assets/docs/design-tokens-responsive.md +148 -0
- package/assets/docs/docs-index.json +93 -0
- package/assets/docs/examples-notion.md +83 -0
- package/assets/docs/examples-school-life.md +74 -0
- package/assets/docs/getting-started.md +132 -0
- package/assets/docs/information-hierarchy.md +81 -0
- package/assets/docs/instances-and-connectors.md +93 -0
- package/assets/docs/lifecycle-and-api.md +158 -0
- package/assets/docs/local-cli.md +124 -0
- package/assets/docs/manifest.md +208 -0
- package/assets/docs/packaging-and-testing.md +115 -0
- package/assets/docs/permissions-and-data.md +131 -0
- package/assets/docs/platform-compatibility.md +62 -0
- package/assets/docs/project-structure.md +102 -0
- package/assets/docs/remote-mcp.md +152 -0
- package/assets/docs/school-life-privacy.md +49 -0
- package/assets/docs/screens-layout-navigation.md +95 -0
- package/assets/docs/sdk-and-mcp.md +182 -0
- package/assets/docs/tool-and-skill.md +163 -0
- package/assets/docs/troubleshooting.md +117 -0
- package/assets/docs/ui-extensions.md +70 -0
- package/assets/docs/ui-runtime-v2.md +273 -0
- package/assets/docs/verification.md +128 -0
- package/assets/plugin_contract.json +222 -1
- package/package.json +1 -1
- package/src/cli.js +27 -3
- package/src/secure-store.js +113 -43
- package/src/workspace.js +297 -86
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# 시작하기와 개발 흐름
|
|
2
|
+
|
|
3
|
+
## 먼저 개발 방식을 고르기
|
|
4
|
+
|
|
5
|
+
| 요구사항 | 권장 방식 |
|
|
6
|
+
|---|---|
|
|
7
|
+
| 텍스트 요약·템플릿처럼 단순한 Tool | 앱의 Plugin Builder |
|
|
8
|
+
| 화면, Search Provider, 알림, 외부 API | 공식 `@morit/cli` 프로젝트 |
|
|
9
|
+
| AI 에이전트가 파일 작성과 검증을 수행 | Local 또는 Remote Plugin MCP |
|
|
10
|
+
| Python 데이터 처리 | `sandbox_python`을 포함한 고급 프로젝트 |
|
|
11
|
+
|
|
12
|
+
세 방식은 결국 같은 `manifest.json`, fragment, `.mplg` 계약을 사용합니다. Builder로 시작한 뒤
|
|
13
|
+
고급 요구가 생기면 소스 프로젝트로 전환할 수 있습니다.
|
|
14
|
+
|
|
15
|
+
## 준비 사항
|
|
16
|
+
|
|
17
|
+
- Node.js 20.12 이상
|
|
18
|
+
- 앱에서 사용할 Morit 계정
|
|
19
|
+
- Cloud Project를 배포할 경우 `developers.moring.co`에서 접근 가능한 조직
|
|
20
|
+
- 저장소 내부 Python 도구를 직접 사용할 경우 Python 3.11 이상
|
|
21
|
+
|
|
22
|
+
개인 키, OAuth client secret, API token은 프로젝트 파일에 기록하지 않습니다. CLI 로그인 정보는
|
|
23
|
+
운영체제 보안 저장소에 두고, Cloud 비밀 값은 Developer Platform의 Secrets에 둡니다.
|
|
24
|
+
|
|
25
|
+
## 첫 프로젝트 만들기
|
|
26
|
+
|
|
27
|
+
빈 디렉터리에서 실행합니다.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx -y @morit/cli plugin setup . \
|
|
31
|
+
--id com.example.study \
|
|
32
|
+
--name "Study" \
|
|
33
|
+
--publisher example
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
이미 유효한 `manifest.json`이 있으면 `--id`, `--name`, `--publisher` 없이 `setup`을 실행할 수
|
|
37
|
+
있습니다. 명령은 로컬 연결 정보 파일 `morit-plugin.json`도 생성합니다. 이 파일에는 Project ID와
|
|
38
|
+
revision만 있으며 인증 정보는 들어가지 않습니다.
|
|
39
|
+
|
|
40
|
+
다음 최소 manifest로 시작할 수 있습니다.
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"schema_version": 2,
|
|
45
|
+
"id": "com.example.study",
|
|
46
|
+
"name": "Study",
|
|
47
|
+
"description": "오늘 할 일을 정리합니다.",
|
|
48
|
+
"publisher": "example",
|
|
49
|
+
"version": "1.0.0",
|
|
50
|
+
"min_morit_version": "1.7.6",
|
|
51
|
+
"max_morit_version": "1.999.999",
|
|
52
|
+
"permissions": [],
|
|
53
|
+
"capabilities": [
|
|
54
|
+
{
|
|
55
|
+
"id": "com.example.study.summary",
|
|
56
|
+
"kind": "tool",
|
|
57
|
+
"title": "할 일 요약",
|
|
58
|
+
"description": "입력한 할 일을 짧게 정리합니다.",
|
|
59
|
+
"permissions": [],
|
|
60
|
+
"runtime": {
|
|
61
|
+
"adapter": "text_template",
|
|
62
|
+
"template": "오늘 할 일을 정리합니다: {query}"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"ui_extensions": [],
|
|
67
|
+
"credentials": [],
|
|
68
|
+
"slash_commands": [],
|
|
69
|
+
"connectors": [],
|
|
70
|
+
"dependencies": [],
|
|
71
|
+
"data_policy": "purge"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
플러그인은 capability나 UI extension을 하나 이상 선언해야 합니다. 큰 배열을 manifest 한 파일에
|
|
76
|
+
넣기보다 `tools/*.json`, `ui/*.json` 같은 fragment로 나누는 편이 충돌과 리뷰 오류를 줄입니다.
|
|
77
|
+
|
|
78
|
+
## 구현 순서
|
|
79
|
+
|
|
80
|
+
1. 사용자가 달성할 작업과 첫 화면의 한 가지 목표를 정합니다.
|
|
81
|
+
2. [프로젝트 구조](project-structure.md)에 맞춰 capability와 UI fragment를 만듭니다.
|
|
82
|
+
3. [Manifest](manifest.md)에 필요한 권한, 연결, 데이터 삭제 정책을 선언합니다.
|
|
83
|
+
4. [화면과 내비게이션](screens-layout-navigation.md)을 먼저 나눈 뒤 컴포넌트를 배치합니다.
|
|
84
|
+
5. 외부 서비스는 [Connector와 인증](authentication.md)을 통해 연결합니다.
|
|
85
|
+
6. `validate`로 참조·권한·파일·서명 전 조건을 확인합니다.
|
|
86
|
+
7. UI가 있으면 preview로 정보 구조와 binding을 점검합니다.
|
|
87
|
+
8. signed `.mplg`를 build하고 다시 열어 서명과 manifest를 확인합니다.
|
|
88
|
+
9. 앱에 설치해 첫 설치, 활성화, 권한 거부, 빈 데이터, 오류, 재시도, 삭제를 실사용합니다.
|
|
89
|
+
10. Cloud Project에 동기화하고 private 배포 후 필요한 경우 public으로 공개합니다.
|
|
90
|
+
|
|
91
|
+
## 로컬 검증과 빌드
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx -y @morit/cli plugin validate .
|
|
95
|
+
npx -y @morit/cli plugin build . --output ./dist/study-1.0.0.mplg
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
UI preview가 필요하면 AI 에이전트에서 `morit_project_preview`를 호출하거나 저장소에서 다음을
|
|
99
|
+
실행합니다.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
python tools/morit_plugin.py preview . --output ./dist/preview.html
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
preview는 HTML 안에서 임의 스크립트를 실행하지 않으며, 실제 앱의 Material 3 렌더링·스크롤·포커스·
|
|
106
|
+
접근성을 보장하지 않습니다. 반드시 앱에서 이어서 확인합니다.
|
|
107
|
+
|
|
108
|
+
## Cloud 연결과 배포
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx -y @morit/cli login
|
|
112
|
+
npx -y @morit/cli plugin add .
|
|
113
|
+
npx -y @morit/cli plugin sync .
|
|
114
|
+
npx -y @morit/cli plugin deploy . --visibility private
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`plugin add`는 현재 조직에 Cloud Project를 만들고 로컬 프로젝트를 연결합니다. 이후 기본 `sync`는
|
|
118
|
+
로컬 파일을 Cloud로 올립니다. `sync --pull`은 Cloud 파일을 가져오며 로컬 변경이 있으면 중단합니다.
|
|
119
|
+
정말 Cloud 사본으로 교체할 때만 `--force`를 함께 사용합니다.
|
|
120
|
+
|
|
121
|
+
## 완료 조건
|
|
122
|
+
|
|
123
|
+
경로를 출력했다는 사실만으로 완료하지 않습니다.
|
|
124
|
+
|
|
125
|
+
- source 파일이 존재하고 다시 읽힌다.
|
|
126
|
+
- validate가 성공한다.
|
|
127
|
+
- UI가 있으면 preview가 생성되고 정보 구조를 확인했다.
|
|
128
|
+
- `.mplg`가 0바이트가 아니며 ZIP과 embedded Ed25519 signature를 다시 검증했다.
|
|
129
|
+
- 실제 앱에서 설치·활성화·기능 실행·오류 복구·삭제를 확인했다.
|
|
130
|
+
- 배포 요청이라면 고유 `deployment_id`, artifact SHA-256, visibility를 확인했다.
|
|
131
|
+
|
|
132
|
+
다음은 [프로젝트 구조와 fragment](project-structure.md)입니다.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# 화면 분리와 정보 계층
|
|
2
|
+
|
|
3
|
+
플러그인은 기능 수보다 사용자가 판단해야 할 순서를 중심으로 화면을 나눕니다. 한 화면에 모든
|
|
4
|
+
설정·목록·상세·오류를 모으면 작은 기기에서 스크롤과 행동 우선순위가 무너집니다.
|
|
5
|
+
|
|
6
|
+
## 한 화면의 질문은 하나
|
|
7
|
+
|
|
8
|
+
화면을 열었을 때 답해야 할 질문을 문장 하나로 씁니다.
|
|
9
|
+
|
|
10
|
+
- 홈 카드: “오늘 확인해야 할 변화가 있는가?”
|
|
11
|
+
- 개요 화면: “오늘 일정과 급식은 무엇인가?”
|
|
12
|
+
- 주간 화면: “이번 주 어느 날이 바쁜가?”
|
|
13
|
+
- 설정 화면: “어떤 학교와 알림 시간을 사용할 것인가?”
|
|
14
|
+
- Response UI: “방금 AI 작업의 핵심 결과는 무엇인가?”
|
|
15
|
+
|
|
16
|
+
질문이 두 개 이상이면 section이나 화면을 분리합니다.
|
|
17
|
+
|
|
18
|
+
## 정보 우선순위
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
1. 화면 제목과 현재 맥락
|
|
22
|
+
2. 사용자가 지금 판단할 핵심 결과
|
|
23
|
+
3. 다음으로 가능한 주 행동 하나
|
|
24
|
+
4. 결과를 설명하는 세부 정보
|
|
25
|
+
5. 드문 행동과 진단 정보
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
주 행동을 여러 개 같은 강도로 표시하지 않습니다. `primary`는 한 화면에 원칙적으로 하나만 두고,
|
|
29
|
+
나머지는 `tonal` 또는 `text`로 낮춥니다.
|
|
30
|
+
|
|
31
|
+
## 목록, 상세, 편집 분리
|
|
32
|
+
|
|
33
|
+
- 목록은 비교와 선택에 집중합니다. 행마다 모든 메타데이터를 표시하지 않습니다.
|
|
34
|
+
- 상세는 선택한 항목의 전체 맥락과 관련 행동을 보여줍니다.
|
|
35
|
+
- 짧은 편집은 `dialog` 또는 `sheet`, 복잡한 편집은 별도 `screen`을 사용합니다.
|
|
36
|
+
- 영구 삭제·연결 해제처럼 되돌리기 어려운 행동은 기본 목록 행동과 분리합니다.
|
|
37
|
+
|
|
38
|
+
## 설정의 계층
|
|
39
|
+
|
|
40
|
+
설정은 기능 소개 화면이 아닙니다. 사용자가 바꿀 수 있는 값만 표시하고 다음처럼 나눕니다.
|
|
41
|
+
|
|
42
|
+
1. 계정과 데이터 원본
|
|
43
|
+
2. 알림과 자동 실행
|
|
44
|
+
3. 표시·정렬 선호
|
|
45
|
+
4. 개인정보와 데이터 삭제
|
|
46
|
+
|
|
47
|
+
설정 값을 Runtime `initial_state`에 선언하고, 저장이 필요한 control에만 `persist: true`를 사용합니다.
|
|
48
|
+
token이나 OAuth 값은 state에 넣지 않고 Credential/Connection 화면으로 연결합니다.
|
|
49
|
+
|
|
50
|
+
## 점진적 공개
|
|
51
|
+
|
|
52
|
+
첫 화면에는 기본 동작에 필요한 최소 정보만 보입니다. 고급 필터, 설명, 부가 지표는 사용자가
|
|
53
|
+
필요할 때 펼치거나 상세 화면에서 봅니다. 단, 중요한 권한·비용·외부 전송 사실을 점진적 공개라는
|
|
54
|
+
이유로 숨기면 안 됩니다.
|
|
55
|
+
|
|
56
|
+
## 좋은 empty와 error 문구
|
|
57
|
+
|
|
58
|
+
empty는 감정적인 문구가 아니라 다음 행동을 안내합니다.
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
나쁜 예: 아직 아무것도 없어요
|
|
62
|
+
좋은 예: 연결한 학교의 이번 주 일정이 없습니다. 다른 주를 선택하거나 학교 설정을 확인하세요.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
오류는 “실패했습니다”에서 끝내지 않습니다.
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
나쁜 예: 문제가 발생했어요
|
|
69
|
+
좋은 예: 학교 정보를 불러오지 못했습니다. 네트워크 연결을 확인한 뒤 다시 시도하세요.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
권한 거부, 계정 만료, 데이터 없음, 일시적 네트워크 오류는 서로 다른 상태입니다. capability가
|
|
73
|
+
구분한 오류 코드를 Host가 사용자용 상태로 표시할 수 있게 유지합니다.
|
|
74
|
+
|
|
75
|
+
## Response UI의 계층
|
|
76
|
+
|
|
77
|
+
Response UI는 대화 본문과 경쟁하지 않습니다. 요약 한 줄, 핵심 결과, 필요한 후속 행동 순으로
|
|
78
|
+
구성하고 원시 실행 로그와 내부 경로를 표시하지 않습니다. 전체 내용은 하나의 응답 container 안에
|
|
79
|
+
두며, 여러 개의 별도 카드가 메시지 아래에 흩어지지 않게 합니다.
|
|
80
|
+
|
|
81
|
+
구체 계약은 [Response UI와 Agent Timeline](ai-response-and-timeline.md)을 참고하세요.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Instance, Connector, 복합 패키지
|
|
2
|
+
|
|
3
|
+
## 사용자별 실행 모델
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
Plugin package
|
|
7
|
+
└─ Installation (사용자에게 설치된 패키지)
|
|
8
|
+
└─ Default Instance (설치와 함께 유지되는 실행·설정 단위)
|
|
9
|
+
├─ Connection A (외부 계정)
|
|
10
|
+
├─ Connection B (외부 계정)
|
|
11
|
+
└─ Child Instance (dependency가 있을 때만 내부 생성)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
사용자가 같은 패키지를 여러 번 설치하거나 root Instance를 복제해 계정을 나누지 않습니다.
|
|
15
|
+
한 번의 설치로 default Instance 하나를 유지하고, 그 안에서 `plugin_connections`를 여러 개 관리합니다.
|
|
16
|
+
`settings`, granted permissions, generation, storage namespace도 이 default Instance를 기준으로
|
|
17
|
+
실행됩니다.
|
|
18
|
+
|
|
19
|
+
## 여러 외부 계정
|
|
20
|
+
|
|
21
|
+
여러 Notion workspace나 여러 API 계정처럼 실제 서비스 계정을 추가하려면 Credential에
|
|
22
|
+
`allow_multiple: true`를 선언합니다. Host는 각 OAuth/API token 연결을 별도 Connection으로 저장하고,
|
|
23
|
+
실행할 때 선택한 `connection_id`를 사용합니다.
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"id": "workspace_account",
|
|
28
|
+
"label": "Workspace 계정",
|
|
29
|
+
"description": "검색할 workspace 연결",
|
|
30
|
+
"kind": "oauth_access_token",
|
|
31
|
+
"oauth_provider": "notion",
|
|
32
|
+
"allow_multiple": true
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`allow_multiple: false`이면 같은 default Instance와 credential 조합에 Connection 하나만 허용합니다.
|
|
37
|
+
이 제한은 다른 플러그인이나 dependency의 credential을 공유한다는 의미가 아닙니다.
|
|
38
|
+
|
|
39
|
+
## Connector의 역할
|
|
40
|
+
|
|
41
|
+
Connector는 다음 외부 실행 정책을 한곳에 모읍니다.
|
|
42
|
+
|
|
43
|
+
- `credential_id`: 어떤 사용자 Connection을 쓸지
|
|
44
|
+
- `cloud_connection_id`: Developer Project의 OAuth provider 설정
|
|
45
|
+
- `cloud_secret_id`: 서버 측 Secret
|
|
46
|
+
- `endpoint`: 공개 HTTPS endpoint
|
|
47
|
+
- `timeout_seconds`, `retry`, `rate_limit`: 복구와 호출량 경계
|
|
48
|
+
|
|
49
|
+
Capability의 `http_json` 또는 `mcp_http` runtime은 `connector_id`를 참조합니다. endpoint나
|
|
50
|
+
credential을 runtime에도 중복 선언하면 값이 Connector와 정확히 일치해야 합니다. 가능한 한
|
|
51
|
+
Connector 한곳에만 둡니다.
|
|
52
|
+
|
|
53
|
+
## Dependency와 Child Instance
|
|
54
|
+
|
|
55
|
+
`dependencies`가 있는 복합 패키지는 Host가 child package를 검증·설치한 뒤 내부 Child Instance를
|
|
56
|
+
만듭니다. Child Instance는 사용자가 같은 플러그인 계정을 하나 더 추가하는 기능이 아닙니다.
|
|
57
|
+
|
|
58
|
+
- 부모는 dependency의 `exposed_capabilities`만 호출합니다.
|
|
59
|
+
- 부모와 자식의 settings, credential, storage, generation은 분리됩니다.
|
|
60
|
+
- 부모 삭제 또는 dependency 변경 시 해당 관계의 Child Instance를 정리합니다.
|
|
61
|
+
- 동일 child package를 다른 부모가 사용해도 실행 상태를 공유하지 않습니다.
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"id": "core",
|
|
66
|
+
"plugin_id": "com.example.core",
|
|
67
|
+
"required": true,
|
|
68
|
+
"package_path": "children/core.mplg",
|
|
69
|
+
"exposed_capabilities": ["com.example.core.search"]
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
부모 capability는 다음처럼 호출합니다.
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"adapter": "child_plugin",
|
|
78
|
+
"dependency_id": "core",
|
|
79
|
+
"capability": "com.example.core.search"
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 선택 기준
|
|
84
|
+
|
|
85
|
+
| 요구사항 | 모델 |
|
|
86
|
+
|---|---|
|
|
87
|
+
| 같은 외부 서비스의 계정 여러 개 | Credential `allow_multiple` + 여러 Connection |
|
|
88
|
+
| 한 설치의 사용자 환경설정 | default Instance settings |
|
|
89
|
+
| 다른 패키지 기능을 포함 | Dependency + 내부 Child Instance |
|
|
90
|
+
| 독립된 제품·권한·배포 수명주기 | 별도 Plugin package |
|
|
91
|
+
|
|
92
|
+
계정 선택 UI는 계정의 표시 이름과 연결 상태를 보여주고 token·secret 자체는 표시하지 않습니다.
|
|
93
|
+
연결 만료는 기능 오류와 구분해 “다시 연결” 동작을 제공해야 합니다.
|
|
@@ -0,0 +1,158 @@
|
|
|
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}` | Instance와 descendant 정리용 API |
|
|
51
|
+
| GET | `/v1/plugins/{plugin_id}` | plugin ID 기준 설치 상세 |
|
|
52
|
+
| PATCH | `/v1/plugins/{plugin_id}` | 호환용 default 설치 설정 |
|
|
53
|
+
|
|
54
|
+
새 앱 흐름은 Installation 목록과 default Instance를 사용합니다. enabled, compatibility, granted
|
|
55
|
+
permission, required Connection, dependency가 모두 준비되어야 state가 `active`가 됩니다. 설정 변경은
|
|
56
|
+
generation을 갱신해 오래된 실행 결과가 새 상태를 덮지 못하게 합니다.
|
|
57
|
+
|
|
58
|
+
## Connection과 OAuth
|
|
59
|
+
|
|
60
|
+
| method | path | 목적 |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| POST | `/v1/plugin-instances/{instance_id}/connections` | Credential Connection 생성 |
|
|
63
|
+
| PATCH | `/v1/plugin-instances/{instance_id}/connections/{connection_id}` | label·default 선택 변경 |
|
|
64
|
+
| POST | `/v1/plugin-instances/{instance_id}/connections/{connection_id}/oauth/start` | Instance OAuth 시작 |
|
|
65
|
+
| DELETE | `/v1/plugin-instances/{instance_id}/connections/{connection_id}` | Connection 해제 |
|
|
66
|
+
| PUT | `/v1/plugins/{plugin_id}/credentials/{credential_id}` | legacy/default API token 설정 |
|
|
67
|
+
| POST | `/v1/plugins/{plugin_id}/credentials/{credential_id}/oauth/start` | legacy/default OAuth 시작 |
|
|
68
|
+
| GET | `/v1/plugins/oauth/callback/{provider_id}` | provider callback |
|
|
69
|
+
| DELETE | `/v1/plugins/{plugin_id}/credentials/{credential_id}` | legacy/default credential 삭제 |
|
|
70
|
+
|
|
71
|
+
`allow_multiple: true`인 Credential은 Connection을 여러 개 만들 수 있습니다. OAuth callback은 state와
|
|
72
|
+
code를 확인한 뒤 Connection을 갱신하며 token을 URL·로그·응답에 반환하지 않습니다.
|
|
73
|
+
|
|
74
|
+
## Capability 실행
|
|
75
|
+
|
|
76
|
+
| method | path | 목적 |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| POST | `/v1/plugin-instances/{instance_id}/capabilities/{capability}/execute` | 특정 Instance 실행 |
|
|
79
|
+
| POST | `/v1/plugins/{plugin_id}/capabilities/{capability}/execute` | default Instance 호환 실행 |
|
|
80
|
+
| POST | `/v1/plugins/search` | enabled Search Provider 통합 검색 |
|
|
81
|
+
| POST | `/v1/plugins/background/run` | due background 실행 |
|
|
82
|
+
|
|
83
|
+
실행 응답:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"plugin_id": "com.example.study",
|
|
88
|
+
"instance_id": "00000000-0000-4000-8000-000000000000",
|
|
89
|
+
"capability": "com.example.study.today",
|
|
90
|
+
"state": "completed",
|
|
91
|
+
"summary": "오늘 일정은 3개입니다.",
|
|
92
|
+
"data": {},
|
|
93
|
+
"evidence": [],
|
|
94
|
+
"error_message": null,
|
|
95
|
+
"error_code": null,
|
|
96
|
+
"retryable": false
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Host는 실행 직전에 actor, owner, enabled, compatible version, generation, permission, Connection,
|
|
101
|
+
dependency, timeout을 다시 확인합니다. 실패는 `error_code`와 `retryable`을 구분하며 성공 summary로
|
|
102
|
+
숨기지 않습니다.
|
|
103
|
+
|
|
104
|
+
## Host action outbox
|
|
105
|
+
|
|
106
|
+
| method | path | 목적 |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| GET | `/v1/plugins/host-actions` | 최대 100개의 pending action lease |
|
|
109
|
+
| POST | `/v1/plugins/host-actions/{event_id}/result` | 플랫폼 적용 성공·실패 ACK |
|
|
110
|
+
|
|
111
|
+
앱은 action을 적용한 뒤 반드시 result를 전송합니다. lease 만료 시 중복 전달될 수 있으므로
|
|
112
|
+
notification key와 storage action은 idempotent하게 처리합니다.
|
|
113
|
+
|
|
114
|
+
## Package, UI 이미지와 삭제
|
|
115
|
+
|
|
116
|
+
| method | path | 목적 |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| GET | `/v1/plugins/{plugin_id}/package` | 설치된 exact `.mplg` attachment 다운로드 |
|
|
119
|
+
| GET | `/v1/plugins/{plugin_id}/assets/{asset_path:path}` | 검증된 package asset 읽기 |
|
|
120
|
+
| POST | `/v1/plugin-instances/{instance_id}/ui-images:fetch` | capability 결과의 원격 이미지 Host proxy |
|
|
121
|
+
| DELETE | `/v1/plugins/{plugin_id}` | Installation, Instance, Connection 삭제 |
|
|
122
|
+
|
|
123
|
+
package 응답은 attachment `Content-Disposition`을 사용합니다. 삭제 시 `purge_data`와 manifest
|
|
124
|
+
`data_policy`를 적용하되 credential, OAuth state, pending Host action은 항상 별도 보안 정리를 거칩니다.
|
|
125
|
+
|
|
126
|
+
Asset API는 활성화된 사용자 소유 설치가 UI Runtime v2에서 실제 참조한 이미지에만 접근할 수
|
|
127
|
+
있습니다. Host는 저장 package의 hash·signature·manifest를 다시 확인하고 확장자와 PNG/JPEG/GIF/WebP
|
|
128
|
+
magic byte, 실제 이미지 포맷을 검사합니다. package asset과 원격 이미지에 공통으로 가로·세로 각각
|
|
129
|
+
4096 px, 애니메이션 128 frame, `width × height × frame 수` 기준 frame 합산 16,000,000 pixel
|
|
130
|
+
한도를 적용합니다. 응답은 private cache, `ETag`, `nosniff`, same-origin resource policy를 사용하며
|
|
131
|
+
traversal·미참조 asset·다른 사용자의 설치는 노출하지 않습니다.
|
|
132
|
+
|
|
133
|
+
원격 이미지 API는 앱의 Host renderer가 호출하는 인증된 binary endpoint입니다. JSON body는
|
|
134
|
+
`{"url":"https://..."}`이고, URL은 `image` 또는 `avatar`의 capability 결과 binding에서
|
|
135
|
+
해석한 값이어야 합니다. 앱과 플러그인은 외부 URL을 직접 fetch하지 않습니다. 요청 actor가 Instance
|
|
136
|
+
owner여야 하고, package Manifest의 `network` 요청과 해당 활성 Instance의 `network` grant가 모두
|
|
137
|
+
유효해야 합니다.
|
|
138
|
+
|
|
139
|
+
Host는 공개 HTTPS만 허용하고 DNS/IP SSRF 검사를 거치며 redirect를 따르지 않습니다. upstream
|
|
140
|
+
응답은 PNG/JPEG/GIF/WebP MIME type, magic byte, 실제 이미지 포맷이 일치해야 합니다. 본문은 최대
|
|
141
|
+
512 KiB이고 위의 공통 dimension·animation 한도를 적용합니다. 성공 응답은 `private, no-store`,
|
|
142
|
+
`Vary: Authorization`, `nosniff`, same-origin resource policy를 사용합니다.
|
|
143
|
+
권한 부족은 `403`, 비활성화·generation/grant 변경은 `409`, unsafe URL·redirect는 `422`, 크기 초과는
|
|
144
|
+
`413`, 지원하지 않거나 손상된 이미지는 `415`, upstream 실패는 `502`, Host network 경계 미설정은
|
|
145
|
+
`503`으로 구분합니다. 소유하지 않은 Instance는 상세 정보를 노출하지 않습니다.
|
|
146
|
+
|
|
147
|
+
## 앱 시작 복구
|
|
148
|
+
|
|
149
|
+
시작할 때 DB Installation과 content-addressed package store를 함께 점검합니다.
|
|
150
|
+
|
|
151
|
+
- DB가 없는 고아 임시/package 파일 정리
|
|
152
|
+
- DB가 가리키는 package가 없거나 hash가 다르면 명확한 load error
|
|
153
|
+
- 오래된 partial Connection/OAuth state 만료
|
|
154
|
+
- dependency Child Instance 재조정
|
|
155
|
+
- pending Host action lease 회수
|
|
156
|
+
|
|
157
|
+
손상된 metadata를 빈 목록으로 바꾸지 않습니다. 목록 없음, 인증 실패, 저장소 손상, network 실패를
|
|
158
|
+
각각 구분해 사용자에게 재시도 또는 복구 동작을 제공합니다.
|
|
@@ -0,0 +1,124 @@
|
|
|
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
|
+
UI preview는 현재 CLI subcommand가 아니라 SDK/MCP 기능입니다.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
python tools/morit_plugin.py preview . --output ./dist/preview.html
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
또는 Local/Remote MCP의 `morit_project_preview`를 사용합니다.
|
|
45
|
+
|
|
46
|
+
## 3. 로그인
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx -y @morit/cli login
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
CLI가 브라우저 device flow를 열고 사용자가 표시된 코드를 승인합니다. Windows는 DPAPI, macOS는
|
|
53
|
+
Keychain, Linux는 Secret Service의 `secret-tool`을 사용합니다. CI는 세션 환경변수
|
|
54
|
+
`MORIT_ACCESS_TOKEN`을 사용할 수 있지만 source, 로그, artifact에 기록하지 않습니다.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx -y @morit/cli logout
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 4. Cloud Project 연결
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx -y @morit/cli plugin add .
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
현재 조직에 Project를 만들고 local source를 연결합니다. 이미 연결된 프로젝트에서 다시 `add`하지
|
|
67
|
+
않습니다.
|
|
68
|
+
|
|
69
|
+
## 5. Sync
|
|
70
|
+
|
|
71
|
+
로컬 → Cloud:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx -y @morit/cli plugin sync .
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Cloud → 로컬:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx -y @morit/cli plugin sync . --pull
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
마지막 sync 이후 로컬 변경이 있으면 pull이 중단됩니다. Cloud 사본으로 덮어쓸 의도가 명확할 때만
|
|
84
|
+
`--pull --force`를 사용합니다. revision 충돌은 새 파일을 읽고 변경을 합친 뒤 다시 push합니다.
|
|
85
|
+
|
|
86
|
+
## 6. Deploy
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
npx -y @morit/cli plugin deploy . --visibility private
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
명령은 최신 source push → local signed build → exact artifact upload → immutable Deployment 생성을
|
|
93
|
+
수행합니다. 응답의 `deployment_id`, `local_artifact_path`, SHA-256, visibility를 기록합니다. 처음에는
|
|
94
|
+
private로 실사용 테스트한 뒤 Marketplace metadata와 공개 준비가 끝났을 때 public으로 변경합니다.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx -y @morit/cli deployment publish <deployment-id> --visibility public
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Cloud와 로컬에서 같은 이름의 package를 다시 만들지 말고 Deployment가 가진 exact artifact hash를
|
|
101
|
+
설치 경로 전체에서 유지합니다.
|
|
102
|
+
|
|
103
|
+
## 조회와 삭제
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npx -y @morit/cli project list
|
|
107
|
+
npx -y @morit/cli project get <project-id>
|
|
108
|
+
npx -y @morit/cli deployment list <project-id>
|
|
109
|
+
npx -y @morit/cli deployment get <deployment-id>
|
|
110
|
+
npx -y @morit/cli project delete <project-id>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
활성 Deployment가 있는 Project 삭제는 서버 정책에 따라 거부될 수 있습니다. 사용자 설치를
|
|
114
|
+
Deployment 정리로 우회 삭제하지 않습니다.
|
|
115
|
+
|
|
116
|
+
## CI 예
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx -y @morit/cli plugin validate . --json
|
|
120
|
+
npx -y @morit/cli plugin build . --output ./dist/plugin.mplg --json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
배포 CI에는 최소 scope의 짧은 수명 token을 주입하고 작업이 끝나면 폐기합니다. build artifact의
|
|
124
|
+
SHA-256과 Deployment의 SHA-256을 비교합니다.
|