@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.
Files changed (35) hide show
  1. package/README.md +53 -18
  2. package/assets/docs/README.md +105 -0
  3. package/assets/docs/ai-response-and-timeline.md +125 -0
  4. package/assets/docs/ai-skill-and-docx-workflow.md +83 -0
  5. package/assets/docs/app-builder.md +56 -0
  6. package/assets/docs/authentication.md +140 -0
  7. package/assets/docs/components.md +159 -0
  8. package/assets/docs/design-tokens-responsive.md +148 -0
  9. package/assets/docs/docs-index.json +93 -0
  10. package/assets/docs/examples-notion.md +83 -0
  11. package/assets/docs/examples-school-life.md +74 -0
  12. package/assets/docs/getting-started.md +132 -0
  13. package/assets/docs/information-hierarchy.md +81 -0
  14. package/assets/docs/instances-and-connectors.md +93 -0
  15. package/assets/docs/lifecycle-and-api.md +158 -0
  16. package/assets/docs/local-cli.md +124 -0
  17. package/assets/docs/manifest.md +208 -0
  18. package/assets/docs/packaging-and-testing.md +115 -0
  19. package/assets/docs/permissions-and-data.md +131 -0
  20. package/assets/docs/platform-compatibility.md +62 -0
  21. package/assets/docs/project-structure.md +102 -0
  22. package/assets/docs/remote-mcp.md +152 -0
  23. package/assets/docs/school-life-privacy.md +49 -0
  24. package/assets/docs/screens-layout-navigation.md +95 -0
  25. package/assets/docs/sdk-and-mcp.md +182 -0
  26. package/assets/docs/tool-and-skill.md +163 -0
  27. package/assets/docs/troubleshooting.md +117 -0
  28. package/assets/docs/ui-extensions.md +70 -0
  29. package/assets/docs/ui-runtime-v2.md +273 -0
  30. package/assets/docs/verification.md +128 -0
  31. package/assets/plugin_contract.json +222 -1
  32. package/package.json +1 -1
  33. package/src/cli.js +27 -3
  34. package/src/secure-store.js +113 -43
  35. 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을 비교합니다.