@morit/cli 1.1.1 → 1.3.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.
@@ -0,0 +1,175 @@
1
+ # Plugin Local Storage와 AI 접근
2
+
3
+ Plugin Local Storage는 Host가 관리하는 영구 JSON 저장소입니다. 데이터는 `user_id + installation_id +
4
+ instance_id + namespace + key`로 분리되며 플러그인 코드가 파일 경로나 DB에 직접 접근하지 않습니다.
5
+ UI, Tool, Skill, Background는 같은 Instance 저장소를 사용합니다.
6
+
7
+ ## Manifest 선언
8
+
9
+ ```json
10
+ {
11
+ "schema_version": 2,
12
+ "permissions": ["storage", "ai_storage"],
13
+ "data_policy": "retain",
14
+ "storage": {
15
+ "version": 2,
16
+ "namespaces": [
17
+ {"id": "default", "max_bytes": 131072, "ai_access": "read"},
18
+ {"id": "tasks", "max_bytes": 262144, "ai_access": "read_write"}
19
+ ],
20
+ "migrations": [
21
+ {
22
+ "from": 1,
23
+ "to": 2,
24
+ "namespace": "tasks",
25
+ "rename": {"today": "today_tasks"},
26
+ "delete": ["legacy_filter"]
27
+ }
28
+ ]
29
+ }
30
+ }
31
+ ```
32
+
33
+ - `storage` 선언에는 schema 2와 `storage` 권한이 필요합니다.
34
+ - namespace는 1~16개, ID는 안전한 plugin identifier입니다.
35
+ - `max_bytes`는 1~256 KiB이고 전체 Installation은 512 KiB 이하입니다.
36
+ - namespace마다 최대 256개 key, key는 최대 80자, 값 하나는 UTF-8 JSON 32 KiB 이하입니다.
37
+ - migration 선언은 최대 64개이며 version은 1부터 1000까지 한 단계씩 증가합니다.
38
+ - 값은 문자열, 유한한 숫자, boolean, `null`, JSON 객체·목록을 사용할 수 있습니다.
39
+ - `ai_access`는 `none`, `read`, `read_write`입니다. 하나라도 AI에 공개하면 Manifest에
40
+ `ai_storage` 권한도 선언해야 합니다.
41
+
42
+ schema 1 플러그인이 기존 `storage` 권한만 요청한 경우 Host는 호환용 `default` namespace(version 1,
43
+ 128 KiB)를 제공합니다. 새 플러그인은 명시적 schema 2 선언을 사용하세요.
44
+
45
+ ## 표준 Storage Tool
46
+
47
+ Host는 저장소를 선언한 각 Instance에 다음 capability를 제공합니다.
48
+
49
+ | Tool | arguments | 동작 |
50
+ |---|---|---|
51
+ | `morit.storage.list` | `namespace`, 선택 `query`, 선택 `limit` | key와 값 검색·목록 |
52
+ | `morit.storage.get` | `namespace`, `key` | 단일 항목 조회 |
53
+ | `morit.storage.create` | `namespace`, `key`, `value` | 없는 key 생성 |
54
+ | `morit.storage.update` | `namespace`, `key`, `value`, `revision` | revision 일치 시 수정 |
55
+ | `morit.storage.delete` | `namespace`, `key`, `revision` | revision 일치 시 삭제 |
56
+
57
+ `namespace`를 생략하면 `default`, 없으면 첫 namespace를 사용합니다. `list.limit`은 1~100이고
58
+ 응답 크기 때문에 일부만 반환하면 `data.has_more`가 `true`입니다. create는 같은 key를 덮어쓰지
59
+ 않으며 update/delete는 직전에 읽은 양의 정수 `revision`을 요구합니다.
60
+
61
+ 모든 결과는 공통 `ExtensionResult` 형태입니다.
62
+
63
+ ```json
64
+ {
65
+ "summary": "task-42 항목을 수정했어요.",
66
+ "data": {
67
+ "namespace": "tasks",
68
+ "item": {
69
+ "key": "task-42",
70
+ "value": {"title": "과학 보고서", "done": true},
71
+ "revision": 3,
72
+ "created_at": "2026-08-17T01:00:00+00:00",
73
+ "updated_at": "2026-08-17T02:00:00+00:00"
74
+ }
75
+ },
76
+ "evidence": [
77
+ {"type": "plugin_storage", "instance_id": "...", "namespace": "tasks", "key": "task-42", "revision": 3}
78
+ ]
79
+ }
80
+ ```
81
+
82
+ ## Saved Entity
83
+
84
+ AI에서 저장·즐겨찾기한 Plugin 항목은 별도 DB 사본을 만들지 않고 같은 Local Storage의
85
+ `saved_entities` namespace를 source of truth로 사용합니다. UI, Tool, Skill과 AI 표준 Storage Tool이
86
+ 같은 key·revision을 읽고 쓰므로 한 경로의 변경이 다른 경로에도 즉시 반영됩니다.
87
+
88
+ ```json
89
+ {
90
+ "type": "school.assignment",
91
+ "title": "과학 보고서",
92
+ "subtitle": "금요일까지",
93
+ "description": "실험 결과 정리",
94
+ "url": "https://school.example/assignments/42",
95
+ "image_url": "https://school.example/images/42.png",
96
+ "favorite": true,
97
+ "data": {"due": "2026-08-21"},
98
+ "source_capability": "school_life.assignments"
99
+ }
100
+ ```
101
+
102
+ `type`과 `title`은 필수입니다. 선택 필드는 `subtitle`, `description`, HTTPS `url`/`image_url`,
103
+ boolean `favorite`, bounded JSON `data`, 안전한 identifier `source_capability`뿐입니다. CRUD는
104
+ `morit.storage.list/get/create/update/delete`를 그대로 사용하며 기존 namespace 권한, quota,
105
+ revision, generation, credential 차단 검사가 동일하게 적용됩니다.
106
+
107
+ ## UI binding과 갱신
108
+
109
+ UI Runtime v2 data source에서 표준 Tool을 그대로 사용합니다.
110
+
111
+ ```json
112
+ {
113
+ "id": "saved_tasks",
114
+ "capability": "morit.storage.list",
115
+ "trigger": "load",
116
+ "query": "",
117
+ "arguments": {"namespace": "tasks", "limit": 20}
118
+ }
119
+ ```
120
+
121
+ 표시는 `data.saved_tasks.data.items`를 source로 사용하는 `list`, 단일 값은
122
+ `{{data.saved_task.data.item.value.title}}`처럼 binding합니다. UI가 Storage create/update/delete를
123
+ 완료하거나 AI 응답이 끝나면 Host가 storage revision을 갱신해 Storage data source만 다시 읽습니다.
124
+ 앱 재개 시에도 갱신합니다. 일반 Tool 결과 data source는 불필요하게 재실행하지 않습니다.
125
+ 서버 Background가 화면과 무관하게 값을 바꾸는 namespace는 source에 `refresh_seconds: 30` 이상을
126
+ 선언해 화면이 열린 동안에도 주기적으로 맞춥니다.
127
+
128
+ Tool·Skill·Background capability는 `storage` 권한을 선언하면 동일 Instance의 `plugin_storage`
129
+ snapshot을 읽고, 결과의 `data.host_actions`에 `plugin_storage_set` 또는
130
+ `plugin_storage_delete`를 반환해 같은 canonical 저장소를 원자적으로 변경할 수 있습니다.
131
+ `namespace`를 명시하면 해당 namespace를 사용합니다.
132
+
133
+ ## AI 권한과 보안 경계
134
+
135
+ 설치 또는 설정에서 사용자가 `ai_storage`를 한 번 허용하면 Morit AI는 매 CRUD마다 다시 승인받지
136
+ 않습니다. 하지만 다음 검사는 항상 적용됩니다.
137
+
138
+ - 현재 사용자에게 속하고 활성화된 Instance만 선택
139
+ - 해당 Installation·Instance·namespace와 package generation 일치
140
+ - `ai_access: read`는 list/get만, `read_write`만 mutation 허용
141
+ - 다른 사용자, 다른 Plugin/Instance, 선언하지 않은 namespace 접근 차단
142
+ - key 이름에 credential, password, secret, token, access token, refresh token, API key,
143
+ private key 계열을 저장하지 못하도록 차단
144
+ - 실행 코드·파일 경로·DB 접근은 제공하지 않음
145
+
146
+ Host가 AI에 노출하는 tool handle은 Instance와 capability를 해시한 불투명 ID입니다. AI는 임의
147
+ `instance_id`를 arguments로 넘겨 scope를 바꿀 수 없습니다.
148
+
149
+ ## 오류 구분
150
+
151
+ | code | 의미 | 처리 |
152
+ |---|---|---|
153
+ | `PLUGIN_STORAGE_PERMISSION_DENIED` | 권한·namespace·AI access 부족 | 설정과 manifest 확인 |
154
+ | `PLUGIN_STORAGE_NOT_FOUND` | key 없음 | create 여부를 사용자 의도에 따라 결정 |
155
+ | `PLUGIN_STORAGE_QUOTA_EXCEEDED` | 값·namespace·Installation 한도 초과 | 데이터를 줄이거나 정리 |
156
+ | `PLUGIN_STORAGE_CONFLICT` | 중복 create, stale revision/generation | list/get 후 최신 revision으로 재시도 |
157
+ | `PLUGIN_STORAGE_INVALID` | key, JSON, query, arguments 형식 오류 | 입력 수정 |
158
+
159
+ 오류를 not-found나 빈 목록으로 바꾸지 마세요. 특히 conflict에서 revision 검사를 제거하면 동시에
160
+ 수정한 사용자 데이터를 잃을 수 있습니다.
161
+
162
+ ## 업데이트, migration, 삭제, 초기화
163
+
164
+ - 업데이트: 같은 Installation/Instance의 데이터는 유지되고 설치 transaction 안에서 version을
165
+ 한 단계씩 올립니다. 각 `(from, namespace)` migration은 한 번만 선언하며 rename 대상 충돌 시
166
+ 업데이트 전체가 실패해 이전 package와 데이터가 유지됩니다.
167
+ - `data_policy: purge`: uninstall 때 Plugin Local Storage를 삭제합니다.
168
+ - `data_policy: retain`: uninstall 뒤 같은 사용자·Plugin Installation ID로 재설치할 때 복구합니다.
169
+ - credential, OAuth state, pending action은 retain과 무관하게 별도 보안 수명주기로 정리됩니다.
170
+ - 사용자가 설정의 **저장 데이터 초기화**를 누르면 해당 Instance 데이터만 삭제합니다. HTTP API는
171
+ `DELETE /v1/plugin-instances/{instance_id}/storage`이며 다른 Instance에는 영향이 없습니다.
172
+ - disable은 데이터를 삭제하지 않지만 읽기·쓰기를 모두 막습니다.
173
+
174
+ 배포 전에는 두 사용자와 둘 이상의 Instance로 격리, read-only AI mutation 차단, quota,
175
+ stale revision/generation, purge/retain, migration 충돌과 재시작 복원을 검증하세요.
@@ -32,7 +32,7 @@ claude mcp add --transport http --scope user \
32
32
 
33
33
  | Tool | 목적 |
34
34
  |---|---|
35
- | `morit_sdk_contract` | 현재 SDK/Host capability contract 전체 반환 |
35
+ | `morit_sdk_contract` | SDK 1.7.8 Theme·Storage·UI/Host capability contract 전체 반환 |
36
36
  | `morit_docs_search` | bundled 공식 문서 검색 |
37
37
  | `morit_docs_get` | 검색 결과의 Markdown 한 파일 읽기 |
38
38
 
@@ -53,7 +53,7 @@ claude mcp add --transport http --scope user \
53
53
  | Tool | 목적 |
54
54
  |---|---|
55
55
  | `morit_project_validate` | 현재 revision의 source 계약 검사 |
56
- | `morit_project_preview` | private HTML preview artifact 생성 |
56
+ | `morit_project_preview` | light/dark 토글을 포함한 script-free private HTML preview 생성 |
57
57
  | `morit_project_source_download` | 편집 가능한 source ZIP 생성 |
58
58
  | `morit_build_start` | immutable build/Deployment 시작 |
59
59
  | `morit_build_status` | build state와 package artifact 조회 |
@@ -108,9 +108,15 @@ AI가 UUID나 경로를 추측하지 않고 list/create/get 결과를 그대로
108
108
  `null`은 파일 삭제입니다. revision이 오래되면 전체 요청을 거부하고 최신 source를 반환하므로
109
109
  `files_get` → 변경 병합 → `files_put`을 반복합니다. conflict를 `force`로 숨기지 않습니다.
110
110
 
111
- 이미지 asset과 child `.mplg`는 검증된 binary envelope로만 JSON 경계를 지나며 최종 source ZIP과
111
+ 이미지 asset과 child `.mplg`는 `morit-base64-v1:<canonical-base64>` binary envelope로만 JSON
112
+ 경계를 지나며 MIME magic과 경로를 검증합니다. 최종 source ZIP과
112
113
  package에서는 원래 바이트로 복원됩니다.
113
114
 
115
+ Theme/Storage를 생성·수정할 때는 contract의 `ui_runtime.theme_*`, `storage_tools`,
116
+ `storage_ai_access`, `storage_limits`를 기준으로 Manifest와 UI fragment를 함께 변경합니다. Remote
117
+ MCP validation은 Host와 같은 잘못된 token, 확정적으로 보이지 않는 색 조합, namespace/permission,
118
+ migration과 binding 오류를 거부하고 정적으로 확정하기 어려운 대비는 warning으로 반환합니다.
119
+
114
120
  ## Build와 다운로드
115
121
 
116
122
  ```text
@@ -1,6 +1,6 @@
1
1
  # 학교 생활 플러그인 개인정보 처리 안내
2
2
 
3
- 최종 수정일: 2026-08-13
3
+ 최종 수정일: 2026-08-17
4
4
 
5
5
  학교 생활 플러그인은 학생이 선택한 학교, 학년, 반을 기준으로 시간표·급식·학사일정을
6
6
  보여 주고 기기 안에서 알림과 아침 브리핑을 구성합니다. 플러그인 설치 전에 아래 데이터
@@ -11,6 +11,7 @@
11
11
  - 사용자가 직접 선택한 학교 코드, 교육청 코드, 학년, 반
12
12
  - 알레르기 필터, 알림 사용 여부와 알림 시간
13
13
  - 중복 알림을 막기 위한 마지막 실행 날짜
14
+ - 사용자가 Morit AI에 추가·수정·완료·삭제를 요청한 학교 할 일
14
15
  - 교육부 NEIS 공개 API가 제공하는 시간표, 급식, 학사일정
15
16
 
16
17
  이 플러그인은 이름, 주민등록번호, 학생 번호, 성적, 연락처, 계정 비밀번호를 요청하거나
@@ -22,6 +23,10 @@
22
23
  사용자의 Plugin Instance 저장소에만 보관합니다. 조회한 공개 학교 정보는 화면 표시와
23
24
  알림 생성에만 사용합니다. 다른 사용자나 다른 Instance와 공유하지 않습니다.
24
25
 
26
+ 학교 할 일은 `tasks` namespace에 저장됩니다. 사용자가 설치/설정에서 `ai_storage` 권한을 허용하면
27
+ Morit AI가 이 namespace를 별도 매 작업 승인 없이 읽고 변경할 수 있습니다. Host는 현재 사용자와
28
+ 활성 Instance를 고정하고 다른 namespace·사용자·플러그인 접근을 차단합니다.
29
+
25
30
  외부 요청은 선언된 NEIS HTTPS endpoint로만 제한되며 Morit의 SSRF filtering proxy를
26
31
  통과합니다. 플러그인 코드에는 운영 credential이나 사용자 token이 포함되지 않습니다.
27
32
 
@@ -36,6 +41,7 @@ Manifest의 data policy는 `purge`입니다. 사용자가 Plugin Instance 또는
36
41
 
37
42
  - 네트워크: NEIS 공개 학교 정보 조회
38
43
  - 저장소: 학교·학년·반과 사용자 설정 저장
44
+ - AI 저장소: 사용자가 허용한 경우 학교 할 일 목록의 조회·추가·수정·삭제
39
45
  - 알림: 사용자가 켠 경우 다음 학교 일정과 아침 브리핑 알림 예약
40
46
  - 백그라운드: 사용자가 켠 경우 공개 학교 정보 갱신과 브리핑 준비
41
47
 
@@ -7,8 +7,10 @@ Morit은 두 MCP 연결을 제공합니다.
7
7
  | Local `@morit/plugin-mcp` | 개발자 PC의 stdio process | 로컬 파일 직접 편집, 빠른 preview/build |
8
8
  | Remote `https://morit-api.moring.co/mcp` | Morit Cloud Streamable HTTP | 파일시스템 없는 AI client, 조직 Project·Secret·Deployment |
9
9
 
10
- 두 연결은 `@morit/cli`와 Host 같은 Plugin contract를 사용합니다. Local source에 접근할 필요가
11
- 없으면 Remote를, 현재 저장소 파일을 직접 고쳐야 하면 Local을 선택합니다.
10
+ 두 연결은 SDK 1.7.8의 `@morit/cli`와 Host Plugin contract를 사용합니다. 계약에는 mode별 Plugin
11
+ Theme, Local Storage/AI access, 표준 Storage Tool, UI binding, A2UI Response catalog와 safe
12
+ preview가 포함됩니다. Local source에 접근할 필요가 없으면 Remote를, 현재 저장소 파일을 직접
13
+ 고쳐야 하면 Local을 선택합니다.
12
14
 
13
15
  ## 준비
14
16
 
@@ -114,7 +116,7 @@ morit_sdk_contract
114
116
  → morit_project_files_get
115
117
  → morit_project_files_put
116
118
  → morit_project_validate
117
- → UI가 있으면 morit_project_preview
119
+ → UI가 있으면 morit_project_preview(light/dark 확인)
118
120
  → morit_build_start / morit_build_status
119
121
  → morit_artifact_download
120
122
  ```
@@ -137,6 +139,21 @@ morit_sdk_contract
137
139
  MCP가 반환한 project ID, revision, artifact ID를 다음 호출에 그대로 사용합니다. 경로를 추측하거나
138
140
  build 전에 완료했다고 보고하지 않습니다.
139
141
 
142
+ PNG/JPEG/GIF/WebP icon·UI asset과 `children/*.mplg`는
143
+ `morit-base64-v1:<canonical-base64>` 문자열로 `files_put`할 수 있습니다. text 경로에 envelope를
144
+ 쓰거나 이미지 확장자와 magic byte가 다르면 Local/Remote MCP가 거부합니다. `files_get`도 같은
145
+ envelope를 반환하므로 수정하지 않은 바이트는 그대로 다음 revision에 유지합니다.
146
+
147
+ Storage 기능을 생성할 때는 먼저 `morit_sdk_contract`의 `permissions`, `storage_tools`,
148
+ `storage_ai_access`, `storage_limits`를 읽고 Manifest `storage`와 UI data source를 함께 작성합니다.
149
+ AI가 쓰는 namespace는 `ai_access: read_write`와 `ai_storage` 권한을 모두 선언합니다. MCP는 package
150
+ runtime 데이터를 읽는 우회 경로가 아니며 개발 source와 artifact만 다룹니다.
151
+
152
+ Response extension을 생성할 때는 `ai_response`와 `ui_runtime` 계약을 함께 읽습니다. MCP 에이전트는
153
+ `point: "response"`, Runtime v2, `a2ui.version/description/schema`, 실제 capability data source를 한
154
+ 단위로 작성해야 합니다. AI가 결과 JSON을 만들게 하지 말고 schema에는 표시 가능한 최소 데이터와
155
+ empty 조건을 명시합니다. validate가 schema·권한·version을 거부하면 파일을 우회 패키징하지 않습니다.
156
+
140
157
  ## Remote MCP 작업 순서
141
158
 
142
159
  ```text
@@ -74,7 +74,7 @@ GET과 POST만 지원하며 redirect를 따라가지 않습니다. endpoint는
74
74
 
75
75
  ### Remote MCP
76
76
 
77
- Morit Host 1.7.6의 plugin outbound MCP adapter는 `mcp_http`입니다. 개발자용 Local/Remote Plugin
77
+ Morit Host 1.7.8의 plugin outbound MCP adapter는 `mcp_http`입니다. 개발자용 Local/Remote Plugin
78
78
  MCP 서버와 이름이 비슷하지만, 설치된 플러그인이 선언한 외부 MCP Tool을 Host 경계에서 호출하는
79
79
  runtime입니다.
80
80
 
@@ -120,6 +120,11 @@ Archive API process에서 실행하지 않고 별도 rootless Docker + gVisor wo
120
120
  AI는 enabled 상태이고 사용자에게 허용된 capability만 발견합니다. 제목과 설명은 모델이 언제 이
121
121
  기능을 선택할지 판단할 수 있게 동작과 입력을 구체적으로 씁니다.
122
122
 
123
+ Slash Command나 UI extension은 선택적 진입점일 뿐 discovery 조건이 아닙니다. 활성 Plugin의 모든
124
+ `tool`·`skill`은 매 AI turn의 actor-scoped capability catalog에 자동 포함됩니다. 설치·업데이트·
125
+ enable/disable·권한 변경·Connection 생성/수정/삭제·OAuth 완료·삭제 뒤 Host는 Instance route cache를
126
+ 즉시 비워 다음 turn과 실행이 새 generation을 사용하게 합니다.
127
+
123
128
  ```json
124
129
  {
125
130
  "id": "school_life.today",
@@ -160,4 +165,8 @@ retry는 Connector에 선언한 bounded 정책만 사용합니다. Tool 하나
160
165
  즉시 포기하지 말고, 오케스트레이터가 대체 Tool·재연결·부분 결과를 선택할 수 있도록 공개 오류를
161
166
  구체적으로 유지합니다.
162
167
 
168
+ 실행 준비 상태의 대표 code는 `EXTENSION_NOT_FOUND`, `EXTENSION_PERMISSION_DENIED`,
169
+ `EXTENSION_CONNECTION_REQUIRED`, `EXTENSION_DISABLED`, `EXTENSION_VERSION_MISMATCH`,
170
+ `EXTENSION_DEPENDENCY_REQUIRED`입니다. 빈 Tool 목록이나 일반 실패 문구로 합치지 마세요.
171
+
163
172
  권한과 알림은 [권한, 설정, 저장소, 알림](permissions-and-data.md)을 참고하세요.
@@ -95,6 +95,10 @@ Tool 하나의 실패가 AI 요청 전체 실패를 뜻하지 않는 경우 대
95
95
  계산한 frame 합산 pixel이 16,000,000 이하여야 합니다.
96
96
  - 원격 이미지 실패를 앱 직접 fetch나 임의 외부 URL widget으로 우회하지 않습니다.
97
97
  - Response UI가 원래 AI 메시지 안의 한 container에 붙는지 확인합니다.
98
+ - Response UI가 안 보이면 `config.a2ui.version`이 `v0.9`인지, schema가 실제 완료 데이터와 맞는지,
99
+ response의 data source가 실행 capability를 가리키는지, Instance 권한이 승인됐는지 확인합니다.
100
+ - 빈 데이터·무관한 답변·중복 결과·Tool 실패에서 Response UI가 생기지 않는 것은 정상입니다. AI가
101
+ catalog 항목을 선택하지 않은 경우에도 서버가 임의 fallback UI를 만들지 않고 텍스트만 남깁니다.
98
102
 
99
103
  ## MCP 오류
100
104
 
@@ -32,6 +32,11 @@ UI extension은 Host의 노출 위치와 Runtime config를 연결합니다.
32
32
  일반 Tool/Skill은 자동으로 `workspace`가 되지 않습니다. AI 작업 공간 UI가 필요할 때만 별도
33
33
  `workspace` extension을 선언합니다. Slash Command와 Tool discovery도 별도 계약입니다.
34
34
 
35
+ `response`는 Runtime v2와 `config.a2ui`가 모두 필수입니다. 활성 Instance의 권한과 capability
36
+ data source가 확인되면 Host가 `plugin.<extension-id>` 컴포넌트로 동적 등록하고, AI가 최종 답변에
37
+ 필요하다고 명시적으로 선택한 경우에만 렌더링합니다. 다른 point에 `a2ui`를 선언하거나 기존
38
+ Runtime v1 Response 형식을 사용하는 것은 오류입니다.
39
+
35
40
  ## Home placement
36
41
 
37
42
  Runtime v2의 `card`와 `action`은 고유 section을 만들 수 있습니다.
@@ -61,8 +66,8 @@ enabled, generation, granted permission, timeout을 Host 실행 경계에서 다
61
66
  ## Runtime v1 호환
62
67
 
63
68
  `ui_schema`가 없는 기존 extension은 `sections`, `actions`, `form`, shorthand `capability`/`label`을
64
- 사용하는 Runtime v1으로 읽힙니다. v1은 기존 설치 호환용입니다. 새 서비스형 화면에는 state,
65
- data source, navigation, theme, Response UI를 함께 사용할 수 있는 Runtime v2를 사용합니다.
69
+ 사용하는 Runtime v1으로 읽힙니다. v1은 Response 이외의 기존 설치 호환용입니다. 새 서비스형
70
+ 화면에는 state, data source, navigation, theme를 함께 사용할 수 있는 Runtime v2를 사용합니다.
66
71
 
67
72
  Runtime v1 action style은 `primary` 또는 `secondary`이고, form field는 `text`, `integer`,
68
73
  `select`만 지원합니다. v1 JSON에 v2 필드를 섞으면 오류입니다.
@@ -97,6 +97,47 @@ child입니다.
97
97
  `aspect_ratio` 0.1~20, `flex` 1~24를 사용합니다. `padding`과 `margin`의 정확한 입력 형태와
98
98
  theme 색상 역할은 [토큰과 반응형](design-tokens-responsive.md)에 있습니다.
99
99
 
100
+ ## Plugin Theme
101
+
102
+ theme를 생략한 기존 플러그인은 Morit의 현재 Material 3 라이트·다크 theme를 그대로 상속합니다.
103
+ 선택적으로 base와 mode별 일부 값만 재정의할 수 있고 누락 값은 항상 Host 값으로 채웁니다.
104
+
105
+ ```json
106
+ {
107
+ "theme": {
108
+ "radius": 18,
109
+ "typography": {"scale": 1.0, "body_weight": 400, "title_weight": 700},
110
+ "surface": {"elevation": 1},
111
+ "border": {"width": 1, "radius": 18},
112
+ "icon": {"size": 22},
113
+ "states": {"disabled_opacity": 0.38},
114
+ "light": {
115
+ "color_scheme": {
116
+ "primary": "#315DA8",
117
+ "on_primary": "#FFFFFF",
118
+ "surface": "#F9F9FF",
119
+ "on_surface": "#1A1B20"
120
+ }
121
+ },
122
+ "dark": {
123
+ "color_scheme": {
124
+ "primary": "#AFC6FF",
125
+ "on_primary": "#002F66",
126
+ "surface": "#111318",
127
+ "on_surface": "#E2E2E9"
128
+ },
129
+ "states": {"selected_color": "primary_container"}
130
+ }
131
+ }
132
+ }
133
+ ```
134
+
135
+ 지원 그룹은 `color_scheme`, `typography`, `surface`, `border`, `icon`, `states`, `radius`,
136
+ `spacing`, `density`와 최상위 `light`, `dark`입니다. mode variant 안에 다시 `light`/`dark`를 중첩할
137
+ 수 없습니다. 명백히 같은 literal 전경/배경은 validate 오류이고, 계산 가능한 대비가 4.5:1보다
138
+ 낮으면 경고합니다. semantic token이나 Host 상속 때문에 정적으로 확정할 수 없는 조합은 Preview와
139
+ 실제 앱에서 확인합니다.
140
+
100
141
  ## State
101
142
 
102
143
  `initial_state`가 허용 state key와 초기값을 선언합니다. key는 영문자로 시작하고 영문·숫자·밑줄을
@@ -144,6 +185,24 @@ arguments에 전달하고, 다른 글자와 섞으면 텍스트 template이 됩
144
185
  {"label": "{{state.period}} 일정"}
145
186
  ```
146
187
 
188
+ ### Local Storage binding
189
+
190
+ 저장소도 같은 data source/action 흐름을 사용합니다.
191
+
192
+ ```json
193
+ {
194
+ "id": "tasks",
195
+ "capability": "morit.storage.list",
196
+ "trigger": "load",
197
+ "query": "",
198
+ "arguments": {"namespace": "tasks", "limit": 20}
199
+ }
200
+ ```
201
+
202
+ `list.source`에는 `data.tasks.data.items`를 사용합니다. Storage mutation이나 AI 응답 완료 뒤 Host가
203
+ Storage source만 갱신하며 앱 재개 시에도 최신 값을 읽습니다. CRUD, 권한과 migration은
204
+ [Plugin Local Storage와 AI 접근](plugin-storage.md)을 참고하세요.
205
+
147
206
  ### Capability 결과 이미지
148
207
 
149
208
  `image`와 `avatar`의 `url`은 literal URL이 아니라 capability 결과를 가리키는 전체
@@ -269,5 +328,16 @@ Host는 source별 실행을 격리하고 중복 실행을 합칩니다. 화면
269
328
  ## Response UI
270
329
 
271
330
  `point: "response"`도 같은 `theme`, `app_bar`, `navigation`, data/state/node/action 계약을
272
- 재사용합니다. 단, 결과는 capability를 실행한 AI 메시지의 response container 안에 렌더링됩니다.
273
- 표현 세부는 [Response UI와 Agent Timeline](ai-response-and-timeline.md)을 참고하세요.
331
+ 재사용합니다. 추가로 `a2ui.version`, 사용 조건을 설명하는 `a2ui.description`, 실제 Tool 결과를
332
+ 검증할 `a2ui.schema`가 필수입니다. 결과는 AI가 catalog 후보를 명시적으로 선택하고 Host의
333
+ 유효성·관련성·중복·권한 검증을 통과했을 때만 해당 AI 메시지의 한 response container 안에
334
+ 렌더링됩니다. 서버는 response를 일괄 생성하거나 shape로 추측하지 않습니다. 상세 계약과 기본
335
+ catalog는 [Response UI와 Agent Timeline](ai-response-and-timeline.md)을 참고하세요.
336
+
337
+ ## Preview와 실제 Host
338
+
339
+ `morit plugin preview . --output ./dist/preview.html` 또는 `morit_project_preview`는 validate를 먼저
340
+ 통과한 config를 실행 코드 없이 안전한 HTML로 렌더링합니다. 상단 토글로 light/dark variant와 Host
341
+ 상속 결과를 비교할 수 있습니다. 같은 manifest를 Flutter 앱이 실제 Material widget으로 렌더링하므로
342
+ node, binding, theme 계약은 공유하지만 focus, 키보드, route stack, 권한, 실제 Tool 호출은 서명
343
+ package를 앱에 설치해 최종 확인합니다.
@@ -10,16 +10,16 @@ SHA-256을 함께 남기면 나중에 같은 결과를 재현할 수 있습니
10
10
 
11
11
  | 패키지 | 크기 | SHA-256 |
12
12
  |---|---:|---|
13
- | `school-life-1.4.0.mplg` | 262758 bytes | `f246d3abff4eac85e1a991d8f436c5381b394858843c95d6d4d88620a1b760b7` |
13
+ | `school-life-1.5.0.mplg` | 263433 bytes | `0939f8956037638d56f7672ac4f9c4d8f6472d58d23840c1fd5baab198c41e29` |
14
14
  | `notion-1.1.0.mplg` | 3044 bytes | `eba57c440b305343c5cf917259e30e50b322272cfadb0ed64d39ecfc47539c4e` |
15
15
 
16
16
  ```powershell
17
17
  python tools/morit_plugin.py verify `
18
- examples/plugins/school-life/dist/school-life-1.4.0.mplg
18
+ examples/plugins/school-life/dist/school-life-1.5.0.mplg
19
19
  python tools/morit_plugin.py verify `
20
20
  examples/plugins/notion/dist/notion-1.1.0.mplg
21
21
  Get-FileHash -Algorithm SHA256 @(
22
- 'examples/plugins/school-life/dist/school-life-1.4.0.mplg',
22
+ 'examples/plugins/school-life/dist/school-life-1.5.0.mplg',
23
23
  'examples/plugins/notion/dist/notion-1.1.0.mplg'
24
24
  )
25
25
  ```
@@ -59,7 +59,7 @@ Slash·Search·Tool routing, UI/Response 계약을 검사합니다. transport fi
59
59
  ```powershell
60
60
  $env:MORIT_E2E_ENV = 'C:\secure\morit-backend.env'
61
61
  $env:MORIT_E2E_API = 'https://morit-api.moring.co'
62
- $env:MORIT_SCHOOL_PLUGIN = 'examples/plugins/school-life/dist/school-life-1.4.0.mplg'
62
+ $env:MORIT_SCHOOL_PLUGIN = 'examples/plugins/school-life/dist/school-life-1.5.0.mplg'
63
63
  $env:MORIT_NOTION_PLUGIN = 'examples/plugins/notion/dist/notion-1.1.0.mplg'
64
64
 
65
65
  # 실제 Notion 승인을 할 때만 0보다 큰 값을 사용합니다.
@@ -108,6 +108,11 @@ Backend live test는 앱 화면과 Android 알림을 누르지 않습니다. rel
108
108
  6. 강제 종료·재실행 뒤 설치·연결·화면 상태 복원
109
109
  7. 비활성화·삭제 뒤 화면·capability·알림 제거
110
110
 
111
+ Response UI는 정상 데이터에서 필요한 컴포넌트 하나만 선택되는지뿐 아니라 빈 데이터, 관련 없는
112
+ 질문, 같은 결과의 중복, schema 불일치, 권한 미승인, Tool 실패에서 컴포넌트가 생성되지 않고 일반
113
+ 텍스트 답변이 유지되는지도 확인합니다. Plugin Response는 `plugin.<extension-id>` 등록 ID와 실제
114
+ 실행 capability의 data source가 일치해야 합니다.
115
+
111
116
  실기기에서 확인하지 않은 항목은 자동 테스트가 통과했더라도 `미검증`으로 남깁니다.
112
117
 
113
118
  ## 5. 결과 기록 형식
@@ -1,5 +1,5 @@
1
1
  {
2
- "sdk_version": "1.7.6",
2
+ "sdk_version": "1.7.8",
3
3
  "manifest_schema_versions": [1, 2],
4
4
  "recommended_schema_version": 2,
5
5
  "package_content_type": "application/vnd.morit.plugin+zip",
@@ -30,7 +30,8 @@
30
30
  "slash_commands",
31
31
  "connectors",
32
32
  "dependencies",
33
- "data_policy"
33
+ "data_policy",
34
+ "storage"
34
35
  ],
35
36
  "fragment_directories": {
36
37
  "capabilities": {"target": "capabilities", "kind": null},
@@ -51,7 +52,10 @@
51
52
  "connector": ["id", "kind", "label", "description", "credential_id", "cloud_connection_id", "cloud_secret_id", "endpoint", "timeout_seconds", "retry", "rate_limit"],
52
53
  "developer": ["name", "url"],
53
54
  "required_secret": ["id", "label", "description", "required"],
54
- "dependency": ["id", "plugin_id", "required", "min_version", "max_version", "package_path", "exposed_capabilities"]
55
+ "dependency": ["id", "plugin_id", "required", "min_version", "max_version", "package_path", "exposed_capabilities"],
56
+ "storage": ["version", "namespaces", "migrations"],
57
+ "storage_namespace": ["id", "max_bytes", "ai_access"],
58
+ "storage_migration": ["from", "to", "namespace", "rename", "delete"]
55
59
  },
56
60
  "capability_runtime_shorthand": ["adapter", "entrypoint", "parameters"],
57
61
  "capability_kinds": ["tool", "skill", "provider", "background", "notification"],
@@ -68,8 +72,75 @@
68
72
  "background",
69
73
  "account_read",
70
74
  "storage",
75
+ "ai_storage",
71
76
  "credentials"
72
77
  ],
78
+ "storage_tools": [
79
+ "morit.storage.list",
80
+ "morit.storage.get",
81
+ "morit.storage.create",
82
+ "morit.storage.update",
83
+ "morit.storage.delete"
84
+ ],
85
+ "storage_ai_access": ["none", "read", "read_write"],
86
+ "saved_entity": {
87
+ "namespace": "saved_entities",
88
+ "tools": [
89
+ "morit.storage.list",
90
+ "morit.storage.get",
91
+ "morit.storage.create",
92
+ "morit.storage.update",
93
+ "morit.storage.delete"
94
+ ],
95
+ "required_value_fields": ["type", "title"],
96
+ "optional_value_fields": [
97
+ "subtitle",
98
+ "description",
99
+ "url",
100
+ "image_url",
101
+ "favorite",
102
+ "data",
103
+ "source_capability"
104
+ ]
105
+ },
106
+ "notification_actions": {
107
+ "types": ["check", "capability", "snooze", "open_plugin", "reply"],
108
+ "max_per_notification": 4,
109
+ "capability_types": ["check", "capability", "reply"],
110
+ "snooze_minutes": {"minimum": 1, "maximum": 1440},
111
+ "dedupe": "event_token_and_action_id",
112
+ "permission_revalidation": true
113
+ },
114
+ "ai_response": {
115
+ "a2ui_version": "v0.9",
116
+ "catalog_id": "https://moring.co/a2ui/catalogs/morit-ai/v1",
117
+ "selection": "ai_explicit_host_validated",
118
+ "selection_limit": 4,
119
+ "model_selection_fields": ["candidate_id", "component", "chart_type"],
120
+ "host_injects_tool_data": true,
121
+ "fallback": "text_only",
122
+ "built_in_components": ["weather", "chart", "image_gallery", "exchange_rate", "article_list", "article_card", "file_result", "image_preview", "video_preview"],
123
+ "validation_gates": ["completed_or_partial", "non_empty", "component_data", "answer_relevance", "not_duplicate", "plugin_schema", "plugin_permission", "plugin_version"],
124
+ "plugin_component_prefix": "plugin.",
125
+ "plugin_metadata_fields": ["version", "description", "schema"],
126
+ "plugin_schema_types": ["object", "array", "string", "number", "integer", "boolean", "null"],
127
+ "plugin_schema_max_bytes": 16384,
128
+ "plugin_schema_max_depth": 6,
129
+ "state_field": "data.__morit_response.state",
130
+ "emitted_states": ["partial", "completed"],
131
+ "image_artifact_mime_types": ["image/png", "image/jpeg", "image/gif", "image/webp"],
132
+ "max_inline_image_bytes": 25165824,
133
+ "max_inline_image_dimension": 4096,
134
+ "max_inline_image_pixels": 16777216
135
+ },
136
+ "storage_limits": {
137
+ "namespaces": 16,
138
+ "keys_per_namespace": 256,
139
+ "value_bytes": 32768,
140
+ "namespace_bytes": 262144,
141
+ "installation_bytes": 524288,
142
+ "migrations": 64
143
+ },
73
144
  "runtime_adapters": [
74
145
  "text_stats",
75
146
  "text_template",
@@ -112,6 +183,7 @@
112
183
  "timeline",
113
184
  "calendar",
114
185
  "chart",
186
+ "table",
115
187
  "form",
116
188
  "dialog",
117
189
  "sheet",
@@ -132,6 +204,7 @@
132
204
  "theme",
133
205
  "initial_state",
134
206
  "data_sources",
207
+ "a2ui",
135
208
  "view"
136
209
  ],
137
210
  "node_fields": ["id", "type", "props", "children", "action", "visible_when"],
@@ -209,10 +282,17 @@
209
282
  "app_bar_fields": ["title", "subtitle", "center_title", "pinned", "leading", "actions"],
210
283
  "navigation_fields": ["type", "items", "selected_state_key", "persist", "label_behavior", "rail_breakpoint"],
211
284
  "menu_item_fields": ["id", "label", "icon", "selected_icon", "value", "show_as", "action"],
212
- "theme_fields": ["color_scheme", "radius", "spacing", "density"],
285
+ "theme_fields": ["color_scheme", "typography", "surface", "border", "icon", "states", "radius", "spacing", "density", "light", "dark"],
286
+ "theme_variant_fields": ["color_scheme", "typography", "surface", "border", "icon", "states", "radius", "spacing", "density"],
287
+ "theme_typography_fields": ["scale", "body_weight", "title_weight"],
288
+ "theme_surface_fields": ["color", "container_color", "elevation"],
289
+ "theme_border_fields": ["color", "width", "radius"],
290
+ "theme_icon_fields": ["color", "size"],
291
+ "theme_state_fields": ["disabled_opacity", "selected_color", "focus_color"],
213
292
  "action_fields": ["type", "capability", "query", "arguments", "store", "target", "source", "values", "persist", "transition", "replace"],
214
293
  "actions": ["invoke", "navigate", "set_state", "refresh", "back"],
215
294
  "navigation_transitions": ["platform", "fade", "slide", "none"],
295
+ "chart_types": ["bar", "line", "donut", "scatter"],
216
296
  "navigation_types": ["tabs", "bar", "rail", "drawer", "adaptive"],
217
297
  "icon_tokens": [
218
298
  "add",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morit/cli",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "Official Morit Developer CLI for Cloud Projects, validation, builds, and deployments",
5
5
  "type": "module",
6
6
  "bin": {