@clack-platform/cli 0.1.0 → 0.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 +44 -10
- package/dist/cli.js +224 -84
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ clack whoami --json
|
|
|
19
19
|
clack logout
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
`login`이 표시한 코드를 앱 **마이페이지 → 계정 → 내 정보 수정하기 → 외부 도구 연결 → 코드로 승인**에서 입력한다. 요청한 권한을 확인하고 승인하면 CLI에 연결된다. 기본 요청 권한은 `profile:read` 하나다. `profile`, `product`, `content`, `creator-content`, `skill`, `platform` 묶음은 각각 읽기·쓰기를 모두 요청한다(`creator-content:publish`, `skill:publish`는 별도 명시가 필요하다). `platform`은 내 콘텐츠의 서버 키·공유 문서 관리 전용 권한이다(아래 "플랫폼 전용 PAT 도구" 절 참고). 소셜 로그인 사용자도 앱에서 승인할 수 있다. 웹 승인 화면은 별도 후속 단계이므로 현재는 앱 승인을 사용한다.
|
|
22
|
+
`login`이 표시한 코드를 앱 **마이페이지 → 계정 → 내 정보 수정하기 → 외부 도구 연결 → 코드로 승인**에서 입력한다. 요청한 권한을 확인하고 승인하면 CLI에 연결된다. 기본 요청 권한은 `profile:read` 하나다. `profile`, `product`, `content`, `creator-content`, `skill`, `platform`, `custom-page` 묶음은 각각 읽기·쓰기를 모두 요청한다(`creator-content:publish`, `skill:publish`, `custom-page:publish`는 별도 명시가 필요하다). 명령별 필요 권한은 각 명령의 `--help`와 아래 절의 권한 표에 적혀 있다. `platform`은 내 콘텐츠의 서버 키·공유 문서 관리 전용 권한이다(아래 "플랫폼 전용 PAT 도구" 절 참고). 소셜 로그인 사용자도 앱에서 승인할 수 있다. 웹 승인 화면은 별도 후속 단계이므로 현재는 앱 승인을 사용한다.
|
|
23
23
|
|
|
24
24
|
수동 발급한 토큰은 `clack login --token`의 숨김 프롬프트에 붙여 넣는다. 비대화형 실행에서는 이 명령의 표준 입력으로 전달할 수 있다. `--token <값>`도 지원하지만 셸 기록을 피하려면 프롬프트나 `CLACK_TOKEN` 환경변수를 사용한다. 토큰 원문은 정상 결과·오류·상세 로그에 출력하지 않는다.
|
|
25
25
|
|
|
@@ -31,6 +31,22 @@ clack token list --json
|
|
|
31
31
|
clack token revoke 123 --yes
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
### 승인 대기 중 실행이 끊기는 경우(에이전트용)
|
|
35
|
+
|
|
36
|
+
`login`은 코드를 발급한 즉시(폴링을 시작하기 전에) 그 요청을 자격 저장소 옆에 0600 권한으로 저장해 둔다. 그래서 승인을 기다리는 도중에 프로세스가 끝나도(에이전트가 턴을 종료하는 경우 등) 승인 자체는 유실되지 않는다 — 다음 실행에서 `clack login --resume`으로 같은 코드를 이어서 기다리면 된다.
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
clack login --no-wait --json # 코드만 발급하고 즉시 반환 (user_code·주소·만료 시각을 JSON으로 출력)
|
|
40
|
+
# … 사용자가 앱에서 승인할 시간을 준다(턴 종료 등) …
|
|
41
|
+
clack login --resume # 같은 요청을 이어서 기다리다가 승인되면 저장한다
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`--no-wait` 없이 그냥 `clack login`을 실행했다가 대기 중 중단된 경우에도 `clack login --resume`으로 이어받을 수 있다. `--resume`은 새 코드를 발급하지 않으며, 저장된 요청이 이미 만료·거부·소비됐으면(`EXPIRED_TOKEN`·`ACCESS_DENIED`·`DEVICE_CODE_CONSUMED`) 그 사실을 분명한 오류 코드로 알리고 자동으로 새 요청을 만들지 않는다 — 이때는 `clack login`을 다시 실행한다. 저장된 요청이 없으면 `DEVICE_REQUEST_NOT_FOUND`, 발급 당시와 다른 `--env`·`--base-url`로 재개를 시도하면 `DEVICE_REQUEST_ORIGIN_MISMATCH`를 반환한다(둘 다 요청을 지우지 않으므로 `--env`를 바로잡아 다시 시도할 수 있다). `--resume`은 `--token`과 함께 쓸 수 없다.
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
clack login --resume --env dev # 발급 당시와 같은 환경이어야 한다. 다르면 DEVICE_REQUEST_ORIGIN_MISMATCH
|
|
48
|
+
```
|
|
49
|
+
|
|
34
50
|
## 주요 명령
|
|
35
51
|
|
|
36
52
|
세부 필드·선택지는 각 명령의 `--help`에서 확인한다.
|
|
@@ -89,7 +105,7 @@ clack channel post create --from-markdown draft.md --status draft
|
|
|
89
105
|
|
|
90
106
|
CLI 안내는 한국어다. `lang`은 API 콘텐츠 언어에 사용한다. `--verbose`는 메서드·경로·시간 계약만 표준 오류로 표시한다. `doctor`는 현재 연결·권한·만료와 서버 메타 API의 한도·읽기/쓰기·MCP·디바이스 연결 상태를 검사한다. 메타 조회 실패는 오류로 반환한다.
|
|
91
107
|
|
|
92
|
-
`--json` 성공 결과는 `{ok:true,data,pagination?,time_contract}`, 실패 결과는 `{ok:false,status,code,message,retry_after?}`다. 시간 문자열은 API 원문과 `time_contract`를 함께 유지한다. 사람용 출력은 알려진 시각 필드에 중앙 파서를 적용하며 `--time utc`로 실제 UTC를 표시할 수 있다. 생년월일과 본문은 시간으로 변환하지 않는다.
|
|
108
|
+
`--json` 성공 결과는 `{ok:true,data,pagination?,time_contract}`, 실패 결과는 `{ok:false,status,code,message,retry_after?}`다. 시간 문자열은 API 원문과 `time_contract`를 함께 유지한다. 사람용 출력은 알려진 시각 필드에 중앙 파서를 적용하며 `--time utc`로 실제 UTC를 표시할 수 있다. 생년월일과 본문은 시간으로 변환하지 않는다. 기본값(`--time local`)은 실행 환경의 로캘·타임존과 무관하게 `YYYY-MM-DD HH:MM KST` 고정 형식으로 표시한다(클랙 서비스는 항상 한국 시간 기준이다).
|
|
93
109
|
|
|
94
110
|
| 종료 코드 | 의미 |
|
|
95
111
|
|---|---|
|
|
@@ -124,7 +140,7 @@ clack skills --json
|
|
|
124
140
|
|
|
125
141
|
원격 MCP는 프로필·상품·게시글·채널 도구 외에 스킬 목록·상세·업로드(`create_skill`→`request_skill_version_upload`→`complete_skill_version`)·심사 제출·공개(`submit_skill_version`, `release_skill_version`, `get_skill_version`), 승인된 HTML 콘텐츠 공개(`publish_creator_content`), 공유 문서 조회·숨김·삭제(`list_shared_documents`, `hide_shared_document`, `delete_shared_document` 등)도 제공한다. 각 도구는 해당 스코프(`skill:*`, `creator-content:publish`, `platform:read`/`platform:write`)를 가진 토큰에만 노출되며, 파괴적 도구는 `confirm:true` 전에는 미리보기만 반환한다. 원격 MCP는 로컬 파일을 읽지 못하므로 스킬 ZIP 전송은 `clack skill push` 또는 로컬 플랫폼 MCP의 `platform_skill_push`를 사용한다.
|
|
126
142
|
|
|
127
|
-
에이전트 스킬은 별도 공개 저장소 [clack-project/skills](https://github.com/clack-project/skills)에서 `npx skills add clack-project/skills`로 설치한다.
|
|
143
|
+
에이전트 스킬은 별도 공개 저장소 [clack-project/skills](https://github.com/clack-project/skills)에서 `npx skills add clack-project/skills`로 설치한다. 모두 10종으로, 일반 계정 관리용 6종(`clack-setup`·`clack-products`·`clack-content`·`clack-channel`·`clack-profile`·`clack-mcp`)과 크리에이터용 4종(`clack-creator-content`·`clack-page`·`clack-skill-package`·`clack-platform-data`)이다. 스킬에 필요한 CLI는 0.1.1 이상이다(`clack-mcp`는 CLI 없이도 쓸 수 있고, 설정 출력에 CLI를 쓸 때는 0.1.0 이상). `clack skills`는 설치를 실행하지 않고 안내만 출력한다. 스킬 목록과 스킬별 요구 CLI 버전도 함께 보여 준다. 스킬 하나만 선택 설치할 때는 `-s clack-products`, 여러 개는 `--skill clack-setup clack-creator-content clack-page`처럼 나열하고, 전역 설치에는 `-g`를 사용한다.
|
|
128
144
|
|
|
129
145
|
## 검증과 배포
|
|
130
146
|
|
|
@@ -140,7 +156,17 @@ GitHub Actions는 Linux·macOS·Windows × Node 20·22에서 `pnpm check`와 패
|
|
|
140
156
|
|
|
141
157
|
## HTML 콘텐츠 개발 테스트
|
|
142
158
|
|
|
143
|
-
크리에이터 콘텐츠는 기존 게시글·포켓과 별도 권한을 사용합니다.
|
|
159
|
+
크리에이터 콘텐츠는 기존 게시글·포켓과 별도 권한을 사용합니다. 앱 확인·심사·공개 기능의 제공 여부는 환경마다 다르며 `clack content config`의 `app_preview_enabled`·`review_enabled`·`publication_enabled`로 확인합니다. CLI로 앱 확인 요건을 우회할 수 없습니다.
|
|
160
|
+
|
|
161
|
+
| 명령 | 필요한 PAT 권한 |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `config` | 없음(로그인 불필요) |
|
|
164
|
+
| `list`, `status`, `preview` | `creator-content:read` |
|
|
165
|
+
| `create`, `upload`, `complete`, `withdraw` | `creator-content:write` |
|
|
166
|
+
| `submit` | `creator-content:write`와 `creator-content:publish` 모두 |
|
|
167
|
+
| `publish`, `unpublish` | `creator-content:publish` |
|
|
168
|
+
|
|
169
|
+
제출 순서는 `upload` → `preview`(같은 계정의 앱에서 열어 확인 완료) → `submit`입니다. 앱 확인 전에 제출하면 `PREVIEW_CONFIRMATION_REQUIRED`(409)가 반환됩니다.
|
|
144
170
|
|
|
145
171
|
```bash
|
|
146
172
|
clack login --env dev --scopes creator-content:read,creator-content:write,creator-content:publish
|
|
@@ -151,7 +177,7 @@ clack content status --env dev <콘텐츠-UUID>
|
|
|
151
177
|
clack content preview --env dev <콘텐츠-UUID> <버전-UUID>
|
|
152
178
|
```
|
|
153
179
|
|
|
154
|
-
`content upload`는 최대 30 MiB의 HTML/ZIP을 체크섬과 함께 비공개 저장소에 전송합니다. 전송 후 완료 응답만 실패하면 출력된 `content complete` 명령으로 같은 업로드를 재처리할 수 있습니다. `--dry-run`은 파일·입력 검사만 수행합니다. `creator-content` 약식 scope는 읽기·쓰기만 요청하며
|
|
180
|
+
`content upload`는 최대 30 MiB의 HTML/ZIP을 체크섬과 함께 비공개 저장소에 전송합니다. 전송 후 완료 응답만 실패하면 출력된 `content complete` 명령으로 같은 업로드를 재처리할 수 있습니다. `--dry-run`은 파일·입력 검사만 수행합니다. `creator-content` 약식 scope는 읽기·쓰기만 요청하며 `submit`·`publish`·`unpublish`에 필요한 `creator-content:publish`는 명시적으로 요청해야 합니다. 정책 내용을 크리에이터 센터에서 확인한 뒤 `--policy-version`으로 동의한 버전을 지정하세요.
|
|
155
181
|
|
|
156
182
|
ZIP에 `clack.content.json` 또는 `.clack/`가 있으면 업로드 전에 선언·경로·크기와 프로필 파일 대응을 확인하고 표준 오류에 경고합니다. 선언이 없는 기존 정적 ZIP도 계속 업로드할 수 있습니다. 경고는 업로드를 막지 않으며 서버의 최종 검증·심사 결과를 대신하지 않습니다. 전송 없이 확인하려면 `content upload ... --dry-run`을 사용하세요.
|
|
157
183
|
|
|
@@ -163,7 +189,7 @@ clack content publish --env dev <콘텐츠-UUID> <버전-UUID>
|
|
|
163
189
|
|
|
164
190
|
## 스킬 패키지 개발 검증
|
|
165
191
|
|
|
166
|
-
`clack.skill.json`과 `SKILL.md`가 있는 디렉터리 또는 ZIP을 검사합니다. 로컬 검증은 로그인 없이 실행할 수 있습니다. 서버
|
|
192
|
+
`clack.skill.json`과 `SKILL.md`가 있는 디렉터리 또는 ZIP을 검사합니다. 로컬 검증은 로그인 없이 실행할 수 있습니다. 서버 경로(`validate --remote`, `push` 등)는 현재 개발 환경에서 기능 플래그가 켜진 계정과 아래 표의 권한을 가진 개인 액세스 토큰이 필요합니다.
|
|
167
193
|
|
|
168
194
|
```sh
|
|
169
195
|
clack skill validate ./my-skill
|
|
@@ -184,7 +210,15 @@ clack skill deprecate <스킬-UUID> --env dev
|
|
|
184
210
|
|
|
185
211
|
`push`는 서버 사전 검증 후 새 스킬 또는 기존 스킬의 버전을 예약하고, 서명된 URL에 ZIP을 전송한 뒤 완료 처리합니다. 완료 응답만 실패하면 출력된 `skill complete` 명령으로 같은 버전을 재처리합니다. `--dry-run`은 서버를 변경하지 않습니다.
|
|
186
212
|
|
|
187
|
-
|
|
213
|
+
| 명령 | 필요한 PAT 권한 |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `validate`(로컬) | 없음(로그인 불필요) |
|
|
216
|
+
| `list`, `get`, `form`, `status` | `skill:read` |
|
|
217
|
+
| `validate --remote`, `push`, `complete`, `cancel` | `skill:write` |
|
|
218
|
+
| `submit` | `skill:write`와 `skill:publish` 모두 |
|
|
219
|
+
| `release`, `deprecate` | `skill:publish` |
|
|
220
|
+
|
|
221
|
+
`clack login --scopes skill` 약식은 읽기·쓰기만 요청하므로 제출·게시·지원 종료에는 `skill:publish`를 추가해야 합니다. 변경 명령은 개인 액세스 토큰(PAT)만 사용합니다. `submit`은 서버에서 검증 완료 상태와 패키지 해시를 조회한 뒤 확인을 받고 심사를 요청합니다. `--dry-run`은 제출 요청 없이 상태만 확인합니다. `release`는 심사를 통과한 버전만 허용하고, `--visibility public`은 별도 라이브러리 등재 승인이 필요합니다. 심사 판정은 관리자 화면에서 처리합니다. `deprecate`는 스킬 전체를 지원 종료 상태로 바꾸며 되돌릴 수 없습니다. 확인 후에만 실행되고(비대화형은 `--yes` 필요), 새 설치·새 제작은 막히지만 기존 콘텐츠가 고정한 버전은 계속 동작합니다.
|
|
188
222
|
|
|
189
223
|
### 스킬 플러그인 정적 검사(plugin-static-v2)
|
|
190
224
|
|
|
@@ -236,14 +270,14 @@ clack platform data patch scores slot --file ./score-patch.json --if-rev 1 --env
|
|
|
236
270
|
clack platform data delete scores slot --env dev
|
|
237
271
|
```
|
|
238
272
|
|
|
239
|
-
`clack mcp config --platform --env dev`는 서버 키 조회와 PAT 개인 도구를 함께 쓸 수 있는 로컬 stdio MCP 설정을 출력합니다. 에이전트 실행 환경에 서버 키 도구용 `CLACK_SERVER_KEY`,
|
|
273
|
+
`clack mcp config --platform --env dev`는 서버 키 조회와 PAT 개인 도구를 함께 쓸 수 있는 로컬 stdio MCP 설정을 출력합니다. 에이전트 실행 환경에 서버 키 도구용 `CLACK_SERVER_KEY`, 스킬·제작·콘텐츠 공개·공유 문서 도구용 `CLACK_TOKEN`(`platform:read`/`platform:write`, 필요 시 `skill:*`, 제작은 `creator-content:write`, 콘텐츠 공개는 `creator-content:publish`)을 각각 필요한 만큼만 설정합니다. 둘 다 로컬 프로세스 환경변수로만 전달되며 CLI 자격 파일에서 자동으로 읽지 않습니다. Claude 설정 조각은 기본 출력, Codex 설정 조각은 `--codex`로 받습니다. 설정에는 키·토큰 원문이 들어가지 않습니다.
|
|
240
274
|
|
|
241
275
|
```sh
|
|
242
276
|
clack mcp config --platform --env dev
|
|
243
277
|
clack mcp config --platform --codex --env dev
|
|
244
278
|
```
|
|
245
279
|
|
|
246
|
-
서버 키(`CLACK_SERVER_KEY`) 도구는 `platform_usage_get`, `platform_server_data_get/list/leaderboard/put/patch/delete`입니다. PAT(`CLACK_TOKEN`) 도구는 `platform_skill_list/get/push/status/submit/release`, `platform_content_publish`, `platform_shared_collections_list`, `platform_shared_documents_list`, `platform_shared_document_get/hide/delete`입니다. `platform_skill_push`는 `dir`(생략 시 현재 디렉터리)의 로컬 스킬 패키지를 검증·업로드합니다. 파괴적 도구(단일 문서 변경, 스킬 심사 제출·공개, 콘텐츠 공개, 공유 문서 숨김·삭제)는 먼저 미리보기와 `confirmation_required`를 반환하고, 입력에 `confirm: true`를 명시해야 실제 요청을 보냅니다. `mcp serve-platform`은 MCP 클라이언트가 실행하는 명령으로, 표준 출력은 JSON-RPC 메시지 전용입니다. 서버 키·PAT의 인증·스코프·테넌트 검사는 모두 API에서 수행합니다.
|
|
280
|
+
서버 키(`CLACK_SERVER_KEY`) 도구는 `platform_usage_get`, `platform_server_data_get/list/leaderboard/put/patch/delete`입니다. PAT(`CLACK_TOKEN`) 도구는 `platform_authoring`, `platform_skill_list/get/push/status/submit/release`, `platform_content_publish`, `platform_shared_collections_list`, `platform_shared_documents_list`, `platform_shared_document_get/hide/delete`입니다. `platform_skill_push`는 `dir`(생략 시 현재 디렉터리)의 로컬 스킬 패키지를 검증·업로드합니다. 파괴적 도구(단일 문서 변경, 스킬 심사 제출·공개, 콘텐츠 공개, 공유 문서 숨김·삭제)는 먼저 미리보기와 `confirmation_required`를 반환하고, 입력에 `confirm: true`를 명시해야 실제 요청을 보냅니다. `mcp serve-platform`은 MCP 클라이언트가 실행하는 명령으로, 표준 출력은 JSON-RPC 메시지 전용입니다. 서버 키·PAT의 인증·스코프·테넌트 검사는 모두 API에서 수행합니다.
|
|
247
281
|
|
|
248
282
|
## 세계관·공개 홈 꾸미기
|
|
249
283
|
|
|
@@ -286,7 +320,7 @@ clack page submit PAGE_ID NEW_VERSION_ID
|
|
|
286
320
|
clack page status PAGE_ID
|
|
287
321
|
```
|
|
288
322
|
|
|
289
|
-
`presentation`은 재호출하면 다른 버전을 만들므로 응답이 유실되면 `status`의 버전 이력을 먼저 확인한다. 같은 업로드를 확정하는 `complete`의 멱등 재시도와 구분한다. 파일은 재사용해도 새 버전은 일일 업로드·논리 저장 한도에 포함된다. 외부 MCP에서도 `revise_custom_page_presentation`으로 같은 작업을
|
|
323
|
+
`presentation`은 재호출하면 다른 버전을 만들므로 응답이 유실되면 `status`의 버전 이력을 먼저 확인한다. 같은 업로드를 확정하는 `complete`의 멱등 재시도와 구분한다. 파일은 재사용해도 새 버전은 일일 업로드·논리 저장 한도에 포함된다. 외부 MCP에서도 `revise_custom_page_presentation`으로 같은 작업을 수행한다. 헤더 값과 화면 표시·안전 영역 규칙은 스킬 저장소의 [페이지 헤더·화면 표시 규칙](https://github.com/clack-project/skills/blob/main/clack-page/references/presentation.md)을 참고한다.
|
|
290
324
|
|
|
291
325
|
### 이미지 화보·캐릭터 가져오기 제작
|
|
292
326
|
|
package/dist/cli.js
CHANGED
|
@@ -86,6 +86,20 @@ function requestTimeContract(method, path) {
|
|
|
86
86
|
].includes(path) || /^\/v4\/posts\/\d+(?:\/comments|\/merchandises)?$/.test(path) || /^\/v4\/merchandises\/\d+$/.test(path) || /^\/v4\/channels\/[^/]+(?:\/posts|\/series)?$/.test(path) || /^\/v4\/(?:channel-posts|channel-series)\/\d+$/.test(path) || /^\/v4\/search\/(?:posts|merchandises-with-post|merchandises-without-post|channel-posts|channels)$/.test(path)) return "utc-v1";
|
|
87
87
|
return "legacy-kst";
|
|
88
88
|
}
|
|
89
|
+
var KST_PARTS = new Intl.DateTimeFormat("en-CA", {
|
|
90
|
+
timeZone: "Asia/Seoul",
|
|
91
|
+
year: "numeric",
|
|
92
|
+
month: "2-digit",
|
|
93
|
+
day: "2-digit",
|
|
94
|
+
hour: "2-digit",
|
|
95
|
+
minute: "2-digit",
|
|
96
|
+
hour12: false
|
|
97
|
+
});
|
|
98
|
+
function formatKst(date) {
|
|
99
|
+
const parts = Object.fromEntries(KST_PARTS.formatToParts(date).map((part) => [part.type, part.value]));
|
|
100
|
+
const hour = parts.hour === "24" ? "00" : parts.hour;
|
|
101
|
+
return `${parts.year}-${parts.month}-${parts.day} ${hour}:${parts.minute} KST`;
|
|
102
|
+
}
|
|
89
103
|
function formatInstant(value2, contract, mode) {
|
|
90
104
|
const iso = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value2);
|
|
91
105
|
const legacyWallClock = contract === "legacy-kst" && /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{3})?$/.test(value2);
|
|
@@ -93,10 +107,7 @@ function formatInstant(value2, contract, mode) {
|
|
|
93
107
|
const source = contract === "legacy-kst" ? (iso ? value2.slice(0, -1) : value2.replace(" ", "T")) + "+09:00" : value2;
|
|
94
108
|
const date = new Date(source);
|
|
95
109
|
if (!Number.isFinite(date.getTime())) return value2;
|
|
96
|
-
return mode === "utc" ? date.toISOString() :
|
|
97
|
-
dateStyle: "medium",
|
|
98
|
-
timeStyle: "long"
|
|
99
|
-
}).format(date);
|
|
110
|
+
return mode === "utc" ? date.toISOString() : formatKst(date);
|
|
100
111
|
}
|
|
101
112
|
var instantKeys = /* @__PURE__ */ new Set(["created_at", "updated_at", "published_at", "hidden_at", "owner_hidden_at", "delete_available_at", "lastBulkBumpedAt", "deleted_at", "expires_at", "revoked_at", "suspended_at", "last_used_at"]);
|
|
102
113
|
function displayData(value2, contract, mode) {
|
|
@@ -139,7 +150,7 @@ function redact(value2) {
|
|
|
139
150
|
// package.json
|
|
140
151
|
var package_default = {
|
|
141
152
|
name: "@clack-platform/cli",
|
|
142
|
-
version: "0.1.
|
|
153
|
+
version: "0.1.1",
|
|
143
154
|
description: "\uD074\uB799 \uACC4\uC815\uC758 \uC0C1\uD488\xB7\uCF58\uD150\uCE20\xB7\uD504\uB85C\uD544\uC744 \uAD00\uB9AC\uD558\uB294 CLI",
|
|
144
155
|
type: "module",
|
|
145
156
|
bin: {
|
|
@@ -606,7 +617,7 @@ async function authoringRequest(client, raw, surface = "cli") {
|
|
|
606
617
|
return client.request(action === "save" ? "PATCH" : "POST", `${path}/${action === "save" ? "form" : action}`, { body });
|
|
607
618
|
}
|
|
608
619
|
function registerAuthoringCommands(program2, runtime2) {
|
|
609
|
-
runtime2.action(program2.command("authoring").description("\uACF5\uAC1C \uC2A4\uD0AC\uB85C \uC81C\uC791\xB7\uC790\uC0B0 \uC5C5\uB85C\uB4DC\xB7\uC774\uBBF8\uC9C0 \uC0DD\uC131\xB7\uD328\uD0A4\uC9D5").requiredOption("--input <file>", "action\uACFC \uC694\uCCAD \uAC12\uC774 \uC788\uB294 JSON \uD30C\uC77C"), async (ctx, _args, opts) => {
|
|
620
|
+
runtime2.action(program2.command("authoring").description("\uACF5\uAC1C \uC2A4\uD0AC\uB85C \uC81C\uC791\xB7\uC790\uC0B0 \uC5C5\uB85C\uB4DC\xB7\uC774\uBBF8\uC9C0 \uC0DD\uC131\xB7\uD328\uD0A4\uC9D5 (creator-content:write)").requiredOption("--input <file>", "action\uACFC \uC694\uCCAD \uAC12\uC774 \uC788\uB294 JSON \uD30C\uC77C"), async (ctx, _args, opts) => {
|
|
610
621
|
const input = await readPlatformBodyFile(opts.input);
|
|
611
622
|
if (ctx.options.dryRun) {
|
|
612
623
|
const parsed = authoringInput.safeParse(input);
|
|
@@ -667,7 +678,7 @@ var ConfigStore = class {
|
|
|
667
678
|
try {
|
|
668
679
|
const stat3 = await lstat3(path);
|
|
669
680
|
if (!stat3.isFile() || stat3.isSymbolicLink()) throw new CliError("\uC124\uC815 \uD30C\uC77C\uC740 \uC77C\uBC18 \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.", "CONFIG_UNSAFE");
|
|
670
|
-
if (name2 === "credentials.json" && process.platform !== "win32") await chmod(path, 384);
|
|
681
|
+
if ((name2 === "credentials.json" || name2 === "device-request.json") && process.platform !== "win32") await chmod(path, 384);
|
|
671
682
|
const body = JSON.parse(await readFile3(path, "utf8"));
|
|
672
683
|
if (!body || typeof body !== "object" || !("profiles" in body) || !body.profiles || typeof body.profiles !== "object" || Array.isArray(body.profiles)) throw new Error("\uD615\uC2DD");
|
|
673
684
|
return body;
|
|
@@ -737,6 +748,21 @@ var ConfigStore = class {
|
|
|
737
748
|
delete all.profiles[validateProfile(profile)];
|
|
738
749
|
await this.write("credentials.json", all);
|
|
739
750
|
}
|
|
751
|
+
/** 발급한 디바이스 요청을 저장해 `login`이 같은 턴에서 끝나도 `login --resume`으로 이어받을 수 있게 한다(E1). */
|
|
752
|
+
async savePendingDeviceRequest(profile, request) {
|
|
753
|
+
const all = await this.read("device-request.json");
|
|
754
|
+
all.profiles[validateProfile(profile)] = request;
|
|
755
|
+
await this.write("device-request.json", all);
|
|
756
|
+
}
|
|
757
|
+
async getPendingDeviceRequest(profile) {
|
|
758
|
+
return (await this.read("device-request.json")).profiles[validateProfile(profile)];
|
|
759
|
+
}
|
|
760
|
+
/** 만료·거부·소비 등으로 더 이상 재개할 수 없는 요청을 정리한다. 네트워크 오류처럼 일시적인 실패에는 호출하지 않는다. */
|
|
761
|
+
async removePendingDeviceRequest(profile) {
|
|
762
|
+
const all = await this.read("device-request.json");
|
|
763
|
+
delete all.profiles[validateProfile(profile)];
|
|
764
|
+
await this.write("device-request.json", all);
|
|
765
|
+
}
|
|
740
766
|
async getConfig(profile) {
|
|
741
767
|
return (await this.read("config.json")).profiles[validateProfile(profile)] ?? {};
|
|
742
768
|
}
|
|
@@ -846,6 +872,57 @@ async function pollDevice(api, request, deps = {
|
|
|
846
872
|
}
|
|
847
873
|
throw new CliError("\uC2B9\uC778 \uCF54\uB4DC\uAC00 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. clack login\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.", "EXPIRED_TOKEN", 410);
|
|
848
874
|
}
|
|
875
|
+
var TERMINAL_DEVICE_FAILURE_CODES = ["EXPIRED_TOKEN", "ACCESS_DENIED", "DEVICE_CODE_CONSUMED", "DEVICE_AUTHORIZATION_NOT_FOUND"];
|
|
876
|
+
function validateVerificationUrl(href2) {
|
|
877
|
+
let verification;
|
|
878
|
+
try {
|
|
879
|
+
verification = new URL(href2);
|
|
880
|
+
} catch {
|
|
881
|
+
throw new CliError("\uC2B9\uC778 \uD398\uC774\uC9C0 \uC8FC\uC18C\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 0);
|
|
882
|
+
}
|
|
883
|
+
if (verification.protocol !== "https:" || !["clack.kr", "dev.clack.kr", "www.clack.kr"].includes(verification.hostname) || verification.username || verification.password || verification.port) throw new CliError("\uC2B9\uC778 \uD398\uC774\uC9C0 \uC8FC\uC18C\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 0);
|
|
884
|
+
return verification;
|
|
885
|
+
}
|
|
886
|
+
function validateFreshDeviceRequest(request) {
|
|
887
|
+
if (!/^dvc_[a-f0-9]{64}$/.test(request.device_code) || !/^[A-Z2-9]{4}-[A-Z2-9]{4}$/.test(request.user_code) || !Number.isFinite(request.expires_in) || request.expires_in <= 0 || request.expires_in > 600 || !Number.isFinite(request.interval) || request.interval < 1) throw new CliError("\uC2B9\uC778 \uC694\uCCAD \uC751\uB2F5\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 0);
|
|
888
|
+
return validateVerificationUrl(request.verification_uri_complete);
|
|
889
|
+
}
|
|
890
|
+
function validatePendingRequest(pending) {
|
|
891
|
+
if (!/^dvc_[a-f0-9]{64}$/.test(pending.device_code) || !/^[A-Z2-9]{4}-[A-Z2-9]{4}$/.test(pending.user_code) || !Number.isFinite(pending.interval) || pending.interval < 1 || !Number.isFinite(Date.parse(pending.expires_at))) {
|
|
892
|
+
throw new CliError("\uC800\uC7A5\uB41C \uB85C\uADF8\uC778 \uC694\uCCAD \uC815\uBCF4\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. clack login\uC73C\uB85C \uC0C8\uB85C \uC2DC\uC791\uD558\uC138\uC694.", "CONFIG_UNSAFE");
|
|
893
|
+
}
|
|
894
|
+
return validateVerificationUrl(pending.verification_uri_complete);
|
|
895
|
+
}
|
|
896
|
+
function newPendingRequest(request, scopes, baseUrl) {
|
|
897
|
+
return {
|
|
898
|
+
device_code: request.device_code,
|
|
899
|
+
user_code: request.user_code,
|
|
900
|
+
verification_uri: request.verification_uri,
|
|
901
|
+
verification_uri_complete: request.verification_uri_complete,
|
|
902
|
+
interval: request.interval,
|
|
903
|
+
scopes,
|
|
904
|
+
base_url: baseUrl,
|
|
905
|
+
expires_at: new Date(Date.now() + request.expires_in * 1e3).toISOString(),
|
|
906
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
function remainingDeviceRequest(pending) {
|
|
910
|
+
return {
|
|
911
|
+
device_code: pending.device_code,
|
|
912
|
+
user_code: pending.user_code,
|
|
913
|
+
verification_uri: pending.verification_uri,
|
|
914
|
+
verification_uri_complete: pending.verification_uri_complete,
|
|
915
|
+
interval: pending.interval,
|
|
916
|
+
expires_in: Math.max(0, Math.floor((Date.parse(pending.expires_at) - Date.now()) / 1e3))
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
async function announceDeviceCode(verification, userCode, opts, json2) {
|
|
920
|
+
process.stderr.write(redactText(`\uC571 \uB9C8\uC774\uD398\uC774\uC9C0 \u2192 \uACC4\uC815 \u2192 \uB0B4 \uC815\uBCF4 \uC218\uC815\uD558\uAE30 \u2192 \uC678\uBD80 \uB3C4\uAD6C \uC5F0\uACB0 \u2192 \uCF54\uB4DC\uB85C \uC2B9\uC778\uC5D0\uC11C ${userCode} \uC785\uB825
|
|
921
|
+
${verification.href}
|
|
922
|
+
`));
|
|
923
|
+
if (opts.qr !== false && process.stderr.isTTY && !json2) qr.generate(verification.href, { small: true }, (value2) => process.stderr.write(value2 + "\n"));
|
|
924
|
+
if (opts.browser && process.stdin.isTTY && !json2) await open2(verification.href).catch(() => process.stderr.write("\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC704 \uC8FC\uC18C\uB098 \uC571\uC5D0\uC11C \uC2B9\uC778\uD558\uC138\uC694.\n"));
|
|
925
|
+
}
|
|
849
926
|
async function tokenInput(option) {
|
|
850
927
|
if (typeof option === "string") return validateToken(option);
|
|
851
928
|
if (process.env.CLACK_TOKEN) return validateToken(process.env.CLACK_TOKEN);
|
|
@@ -858,11 +935,12 @@ async function tokenInput(option) {
|
|
|
858
935
|
return validateToken(value2);
|
|
859
936
|
}
|
|
860
937
|
function registerAuthCommands(program2, runtime2, store2) {
|
|
861
|
-
runtime2.action(program2.command("login").description("\uC571\uC5D0\uC11C \uC2B9\uC778\uD558\uAC70\uB098 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC73C\uB85C \uC5F0\uACB0").option("--token [pat]", "\uD1A0\uD070 \uC785\uB825. \uAC12\uC744 \uC0DD\uB7B5\uD558\uBA74 \uC228\uAE40 \uD504\uB86C\uD504\uD2B8 \uB610\uB294 \uD45C\uC900 \uC785\uB825 \uC0AC\uC6A9").option("--scopes <scopes>", "\uC694\uCCAD \uAD8C\uD55C (\uC27C\uD45C \uAD6C\uBD84)", "profile:read").option("--no-browser", "\uBE0C\uB77C\uC6B0\uC800 \uC790\uB3D9 \uC5F4\uAE30 \uC0DD\uB7B5").option("--no-qr", "QR \uCF54\uB4DC \uCD9C\uB825 \uC0DD\uB7B5 (\uC5D0\uC774\uC804\uD2B8\xB7\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0 \uC801\uD569)").option("--label <name>", "\uAE30\uAE30 \uC774\uB984", hostname()), async (ctx, _args, opts) => {
|
|
938
|
+
runtime2.action(program2.command("login").description("\uC571\uC5D0\uC11C \uC2B9\uC778\uD558\uAC70\uB098 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC73C\uB85C \uC5F0\uACB0").option("--token [pat]", "\uD1A0\uD070 \uC785\uB825. \uAC12\uC744 \uC0DD\uB7B5\uD558\uBA74 \uC228\uAE40 \uD504\uB86C\uD504\uD2B8 \uB610\uB294 \uD45C\uC900 \uC785\uB825 \uC0AC\uC6A9").option("--scopes <scopes>", "\uC694\uCCAD \uAD8C\uD55C (\uC27C\uD45C \uAD6C\uBD84). product \uAC19\uC740 \uBB36\uC74C \uC774\uB984\uC740 :read\xB7:write\uB9CC \uC694\uCCAD\uD558\uBA70, \uC81C\uCD9C\xB7\uAC8C\uC2DC\uC5D0 \uD544\uC694\uD55C *:publish\uB294 \uB530\uB85C \uC801\uB294\uB2E4", "profile:read").option("--no-browser", "\uBE0C\uB77C\uC6B0\uC800 \uC790\uB3D9 \uC5F4\uAE30 \uC0DD\uB7B5").option("--no-qr", "QR \uCF54\uB4DC \uCD9C\uB825 \uC0DD\uB7B5 (\uC5D0\uC774\uC804\uD2B8\xB7\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0 \uC801\uD569)").option("--label <name>", "\uAE30\uAE30 \uC774\uB984", hostname()).option("--no-wait", "\uCF54\uB4DC\uB9CC \uBC1C\uAE09\uD558\uACE0 \uC2B9\uC778\uC744 \uAE30\uB2E4\uB9AC\uC9C0 \uC54A\uC74C (\uAC19\uC740 \uD134\uC744 \uB05D\uB0B4\uC57C \uD558\uB294 \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC801\uD569, \uC774\uD6C4 clack login --resume\uC73C\uB85C \uC774\uC5B4\uBC1B\uC74C)").option("--resume", "\uC774\uC804\uC5D0 \uBC1C\uAE09\uD588\uC9C0\uB9CC \uC544\uC9C1 \uB9CC\uB8CC\uB418\uC9C0 \uC54A\uC740 \uC694\uCCAD\uC744 \uC774\uC5B4\uC11C \uAE30\uB2E4\uB9BC (\uC0C8 \uCF54\uB4DC\uB97C \uBC1C\uAE09\uD558\uC9C0 \uC54A\uC74C)"), async (ctx, _args, opts) => {
|
|
862
939
|
if (ctx.options.dryRun) throw new CliError("login\uC740 --dry-run\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
863
940
|
const resolved = await store2.resolve(ctx.options, true);
|
|
864
941
|
let credential;
|
|
865
942
|
if (opts.token !== void 0 || process.env.CLACK_TOKEN) {
|
|
943
|
+
if (opts.resume) throw new CliError("--resume\uC740 --token\uACFC \uD568\uAED8 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
866
944
|
const token = await tokenInput(opts.token);
|
|
867
945
|
const api = new ApiClient({ baseUrl: resolved.baseUrl, token, verbose: ctx.options.verbose });
|
|
868
946
|
const row = await currentToken(api, token);
|
|
@@ -873,36 +951,77 @@ function registerAuthCommands(program2, runtime2, store2) {
|
|
|
873
951
|
credential.user = { id: result.data.id, name: result.data.name, avatar: result.data.avatar };
|
|
874
952
|
}
|
|
875
953
|
} else {
|
|
876
|
-
|
|
877
|
-
if (
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
954
|
+
let pending;
|
|
955
|
+
if (opts.resume) {
|
|
956
|
+
const stored = await store2.getPendingDeviceRequest(resolved.profile);
|
|
957
|
+
if (!stored) throw new CliError("\uB300\uAE30 \uC911\uC778 \uB85C\uADF8\uC778 \uC694\uCCAD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. clack login\uC73C\uB85C \uC0C8\uB85C \uC2DC\uC791\uD558\uC138\uC694.", "DEVICE_REQUEST_NOT_FOUND", 404);
|
|
958
|
+
if ((ctx.options.env || ctx.options.baseUrl) && resolved.baseUrl !== stored.base_url) {
|
|
959
|
+
throw new CliError(`\uB300\uAE30 \uC911\uC778 \uC694\uCCAD\uC740 ${stored.base_url}\uC5D0 \uBC1C\uAE09\uB418\uC5C8\uC2B5\uB2C8\uB2E4. --env\xB7--base-url \uC5C6\uC774 clack login --resume\uC744 \uC2E4\uD589\uD558\uC138\uC694.`, "DEVICE_REQUEST_ORIGIN_MISMATCH", 401);
|
|
960
|
+
}
|
|
961
|
+
pending = stored;
|
|
962
|
+
} else {
|
|
963
|
+
const scopes = parseScopes(opts.scopes);
|
|
964
|
+
if (typeof opts.label !== "string" || !opts.label.trim() || opts.label.length > 120) throw new CliError("\uAE30\uAE30 \uC774\uB984\uC740 1~120\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
965
|
+
const { data: request } = await ctx.api.request("POST", "/v4/auth/device/code", { auth: false, body: {
|
|
966
|
+
client_name: "cli",
|
|
967
|
+
client_version: VERSION,
|
|
968
|
+
device_name: opts.label,
|
|
969
|
+
scopes
|
|
970
|
+
} });
|
|
971
|
+
rememberSecret(request.device_code);
|
|
972
|
+
validateFreshDeviceRequest(request);
|
|
973
|
+
pending = newPendingRequest(request, scopes, resolved.baseUrl);
|
|
974
|
+
await store2.savePendingDeviceRequest(resolved.profile, pending);
|
|
975
|
+
}
|
|
976
|
+
rememberSecret(pending.device_code);
|
|
977
|
+
const verification = validatePendingRequest(pending);
|
|
978
|
+
await announceDeviceCode(verification, pending.user_code, opts, Boolean(ctx.options.json));
|
|
979
|
+
if (opts.wait === false) {
|
|
980
|
+
const expiry = formatInstant(pending.expires_at, "utc-v1", ctx.options.time ?? "local");
|
|
981
|
+
if (ctx.options.json) {
|
|
982
|
+
ctx.output({
|
|
983
|
+
status: "pending",
|
|
984
|
+
user_code: pending.user_code,
|
|
985
|
+
verification_uri: pending.verification_uri,
|
|
986
|
+
verification_uri_complete: pending.verification_uri_complete,
|
|
987
|
+
expires_at: pending.expires_at,
|
|
988
|
+
interval: pending.interval,
|
|
989
|
+
message: "\uC571\uC5D0\uC11C \uC2B9\uC778\uD55C \uB4A4 \uC774\uC5B4\uC11C \uBC1B\uC73C\uB824\uBA74 clack login --resume\uC744 \uC2E4\uD589\uD558\uC138\uC694."
|
|
990
|
+
});
|
|
991
|
+
} else {
|
|
992
|
+
process.stdout.write(redactText([
|
|
993
|
+
`\uC2B9\uC778\uC744 \uAE30\uB2E4\uB9AC\uC9C0 \uC54A\uACE0 \uBC18\uD658\uD569\uB2C8\uB2E4. (\uD504\uB85C\uD544: ${resolved.profile}, \uC8FC\uC18C: ${pending.base_url})`,
|
|
994
|
+
`\uCF54\uB4DC\uB294 ${expiry}\uAE4C\uC9C0 \uC720\uD6A8\uD569\uB2C8\uB2E4.`,
|
|
995
|
+
"\uC571\uC5D0\uC11C \uC2B9\uC778\uD55C \uB4A4 \uC774\uC5B4\uC11C \uBC1B\uC73C\uB824\uBA74: clack login --resume"
|
|
996
|
+
].join("\n") + "\n"));
|
|
997
|
+
}
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
1000
|
+
const api = opts.resume ? new ApiClient({ baseUrl: pending.base_url, verbose: ctx.options.verbose }) : ctx.api;
|
|
1001
|
+
let grant;
|
|
1002
|
+
try {
|
|
1003
|
+
grant = await pollDevice(api, remainingDeviceRequest(pending));
|
|
1004
|
+
} catch (error2) {
|
|
1005
|
+
if (error2 instanceof CliError && TERMINAL_DEVICE_FAILURE_CODES.includes(error2.code)) {
|
|
1006
|
+
await store2.removePendingDeviceRequest(resolved.profile).catch(() => {
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
throw error2;
|
|
1010
|
+
}
|
|
894
1011
|
const token = validateToken(grant.token);
|
|
895
|
-
if (!Array.isArray(grant.scopes) || grant.scopes.some((scope) => !scopes.includes(scope)) || !Number.isFinite(Date.parse(grant.expires_at)) || !grant.user || !Number.isSafeInteger(grant.user.id)) throw new CliError("\uAD50\uBD80 \uC751\uB2F5\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC571\uC5D0\uC11C \uC5F0\uACB0 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694.", "INVALID_RESPONSE", 0);
|
|
896
|
-
credential = { token, base_url:
|
|
1012
|
+
if (!Array.isArray(grant.scopes) || grant.scopes.some((scope) => !pending.scopes.includes(scope)) || !Number.isFinite(Date.parse(grant.expires_at)) || !grant.user || !Number.isSafeInteger(grant.user.id)) throw new CliError("\uAD50\uBD80 \uC751\uB2F5\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC571\uC5D0\uC11C \uC5F0\uACB0 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694.", "INVALID_RESPONSE", 0);
|
|
1013
|
+
credential = { token, base_url: pending.base_url, scopes: grant.scopes, expires_at: grant.expires_at, user: grant.user };
|
|
1014
|
+
await store2.removePendingDeviceRequest(resolved.profile).catch(() => {
|
|
1015
|
+
});
|
|
897
1016
|
}
|
|
898
1017
|
await store2.saveCredential(resolved.profile, credential);
|
|
899
|
-
await store2.setConfig(resolved.profile, "base_url",
|
|
1018
|
+
await store2.setConfig(resolved.profile, "base_url", credential.base_url);
|
|
900
1019
|
if (ctx.options.json) {
|
|
901
1020
|
ctx.output({ profile: resolved.profile, user: credential.user ?? null, scopes: credential.scopes, expires_at: credential.expires_at, message: "\uC5F0\uACB0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." });
|
|
902
1021
|
} else {
|
|
903
1022
|
const expiry = formatInstant(credential.expires_at, "utc-v1", ctx.options.time ?? "local");
|
|
904
1023
|
process.stdout.write(redactText([
|
|
905
|
-
`\uC5F0\uACB0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (\uD504\uB85C\uD544: ${resolved.profile}, \uC8FC\uC18C: ${
|
|
1024
|
+
`\uC5F0\uACB0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (\uD504\uB85C\uD544: ${resolved.profile}, \uC8FC\uC18C: ${credential.base_url})`,
|
|
906
1025
|
credential.user ? `\uC0AC\uC6A9\uC790: ${credential.user.name ?? "\uC774\uB984 \uC5C6\uC74C"} (#${credential.user.id})` : "\uC0AC\uC6A9\uC790: profile:read \uAD8C\uD55C\uC774 \uC5C6\uC5B4 \uC870\uD68C\uD558\uC9C0 \uC54A\uC74C",
|
|
907
1026
|
`\uAD8C\uD55C: ${credential.scopes.join(", ")}`,
|
|
908
1027
|
`\uB9CC\uB8CC: ${expiry}`
|
|
@@ -2086,15 +2205,15 @@ var keyId = (value2) => {
|
|
|
2086
2205
|
return id2;
|
|
2087
2206
|
};
|
|
2088
2207
|
function registerContentCommands(program2, runtime2) {
|
|
2089
|
-
const content = program2.command("content").description("HTML \uCF58\uD150\uCE20 \uB4F1\uB85D\xB7\uC571 \uD655\uC778\xB7\uC2EC\uC0AC \uAD00\uB9AC");
|
|
2090
|
-
runtime2.action(content.command("config"), async (ctx) => ctx.output(await ctx.api.request("GET", "/v4/creator/config", { auth: false })));
|
|
2091
|
-
runtime2.action(content.command("list"), async (ctx) => ctx.output(await ctx.api.request("GET", "/v4/creator/contents")));
|
|
2092
|
-
runtime2.action(content.command("status <id>"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}`)));
|
|
2093
|
-
runtime2.action(content.command("create").requiredOption("--title <text>", "\uC81C\uBAA9").option("--description <text>", "\uC124\uBA85", "").option("--kind <kind>", "html|gallery|slideshow|video", "html").requiredOption("--policy-version <version>", "\uB3D9\uC758\uD55C \uCF58\uD150\uCE20 \uC81C\uACF5 \uC815\uCC45 \uBC84\uC804"), async (ctx, _args, opts) => {
|
|
2208
|
+
const content = program2.command("content").description("HTML \uCF58\uD150\uCE20 \uB4F1\uB85D\xB7\uC571 \uD655\uC778\xB7\uC2EC\uC0AC \uAD00\uB9AC (creator-content:read\xB7write\xB7publish)");
|
|
2209
|
+
runtime2.action(content.command("config").description("\uCF58\uD150\uCE20 \uAE30\uB2A5 \uC81C\uACF5 \uC5EC\uBD80\xB7\uC124\uC815 \uC870\uD68C (\uB85C\uADF8\uC778 \uBD88\uD544\uC694)"), async (ctx) => ctx.output(await ctx.api.request("GET", "/v4/creator/config", { auth: false })));
|
|
2210
|
+
runtime2.action(content.command("list").description("\uB0B4 \uCF58\uD150\uCE20 \uBAA9\uB85D (creator-content:read)"), async (ctx) => ctx.output(await ctx.api.request("GET", "/v4/creator/contents")));
|
|
2211
|
+
runtime2.action(content.command("status <id>").description("\uCF58\uD150\uCE20\uC640 \uBC84\uC804\uBCC4 \uC5C5\uB85C\uB4DC\xB7\uC2EC\uC0AC\xB7\uACF5\uAC1C \uC0C1\uD0DC (creator-content:read)"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}`)));
|
|
2212
|
+
runtime2.action(content.command("create").description("\uCF58\uD150\uCE20 \uCD08\uC548 \uB4F1\uB85D (creator-content:write)").requiredOption("--title <text>", "\uC81C\uBAA9").option("--description <text>", "\uC124\uBA85", "").option("--kind <kind>", "html|gallery|slideshow|video", "html").requiredOption("--policy-version <version>", "\uB3D9\uC758\uD55C \uCF58\uD150\uCE20 \uC81C\uACF5 \uC815\uCC45 \uBC84\uC804"), async (ctx, _args, opts) => {
|
|
2094
2213
|
const body = parse(createSchema, { title: opts.title, description: opts.description, kind: opts.kind, policy_version: opts.policyVersion });
|
|
2095
2214
|
await mutate(ctx, "POST", "/v4/creator/contents", body);
|
|
2096
2215
|
});
|
|
2097
|
-
runtime2.action(content.command("upload <id> <file>").addOption(new Option("--header <mode>", "\uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"])).addOption(new Option("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"])), async (ctx, [id2, file], opts) => {
|
|
2216
|
+
runtime2.action(content.command("upload <id> <file>").description("HTML/ZIP \uC5C5\uB85C\uB4DC\uB85C \uC0C8 \uBE44\uACF5\uAC1C \uBC84\uC804 \uC0DD\uC131 (creator-content:write)").addOption(new Option("--header <mode>", "\uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"])).addOption(new Option("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"])), async (ctx, [id2, file], opts) => {
|
|
2098
2217
|
const path = `/v4/creator/contents/${uuid(id2)}`;
|
|
2099
2218
|
const prepared = await prepareContentFile(file);
|
|
2100
2219
|
for (const warning of prepared.warnings) process.stderr.write(`\uD328\uD0A4\uC9C0 \uACBD\uACE0: ${warning}
|
|
@@ -2110,39 +2229,39 @@ function registerContentCommands(program2, runtime2) {
|
|
|
2110
2229
|
throw error2;
|
|
2111
2230
|
}
|
|
2112
2231
|
});
|
|
2113
|
-
runtime2.action(content.command("complete <id> <upload-id>"), async (ctx, [id2, uploadId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/uploads/${uuid(uploadId)}/complete`));
|
|
2114
|
-
runtime2.action(content.command("preview <id> <version-id>"), async (ctx, [id2, versionId]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/preview`)));
|
|
2115
|
-
runtime2.action(content.command("submit <id> <version-id>"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/submit`, { publish_on_approval: true }, "\uC774 \uBC84\uC804\uC744 \uC2EC\uC0AC\uD558\uACE0 \uD1B5\uACFC\uD558\uBA74 \uACF5\uAC1C\uD560\uAE4C\uC694?"));
|
|
2116
|
-
runtime2.action(content.command("withdraw <id> <version-id>"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/review/cancel`, void 0, "\uC774 \uBC84\uC804\uC758 \uC2EC\uC0AC\uB97C \uCDE8\uC18C\uD560\uAE4C\uC694?"));
|
|
2117
|
-
runtime2.action(content.command("publish <id> <version-id>"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/publish`, void 0, "\uC2B9\uC778\uB41C \uC774 \uBC84\uC804\uC744 \uACF5\uAC1C\uD560\uAE4C\uC694?"));
|
|
2118
|
-
runtime2.action(content.command("unpublish <id>"), async (ctx, [id2]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/unpublish`, void 0, "\uCF58\uD150\uCE20 \uAC8C\uC2DC\uB97C \uC911\uB2E8\uD560\uAE4C\uC694?"));
|
|
2232
|
+
runtime2.action(content.command("complete <id> <upload-id>").description("\uC5C5\uB85C\uB4DC \uC644\uB8CC \uCC98\uB9AC \uC7AC\uC2DC\uB3C4 (creator-content:write)"), async (ctx, [id2, uploadId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/uploads/${uuid(uploadId)}/complete`));
|
|
2233
|
+
runtime2.action(content.command("preview <id> <version-id>").description("\uC571 \uD655\uC778\uC6A9 \uBBF8\uB9AC\uBCF4\uAE30 \uB9C1\uD06C \uC870\uD68C (creator-content:read)"), async (ctx, [id2, versionId]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/preview`)));
|
|
2234
|
+
runtime2.action(content.command("submit <id> <version-id>").description("\uC571\uC5D0\uC11C \uD655\uC778 \uC644\uB8CC\uD55C \uBC84\uC804\uC758 \uC2EC\uC0AC \uC81C\uCD9C, \uD1B5\uACFC \uC2DC \uACF5\uAC1C (creator-content:write\uC640 creator-content:publish \uB458 \uB2E4 \uD544\uC694)"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/submit`, { publish_on_approval: true }, "\uC774 \uBC84\uC804\uC744 \uC2EC\uC0AC\uD558\uACE0 \uD1B5\uACFC\uD558\uBA74 \uACF5\uAC1C\uD560\uAE4C\uC694?"));
|
|
2235
|
+
runtime2.action(content.command("withdraw <id> <version-id>").description("\uC9C4\uD589 \uC911\uC778 \uC2EC\uC0AC \uCDE8\uC18C (creator-content:write)"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/review/cancel`, void 0, "\uC774 \uBC84\uC804\uC758 \uC2EC\uC0AC\uB97C \uCDE8\uC18C\uD560\uAE4C\uC694?"));
|
|
2236
|
+
runtime2.action(content.command("publish <id> <version-id>").description("\uC2B9\uC778\uB410\uC9C0\uB9CC \uACF5\uAC1C\uD558\uC9C0 \uC54A\uC740 \uBC84\uC804 \uACF5\uAC1C (creator-content:publish)"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/publish`, void 0, "\uC2B9\uC778\uB41C \uC774 \uBC84\uC804\uC744 \uACF5\uAC1C\uD560\uAE4C\uC694?"));
|
|
2237
|
+
runtime2.action(content.command("unpublish <id>").description("\uCF58\uD150\uCE20 \uAC8C\uC2DC \uC911\uB2E8 (creator-content:publish)"), async (ctx, [id2]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/unpublish`, void 0, "\uCF58\uD150\uCE20 \uAC8C\uC2DC\uB97C \uC911\uB2E8\uD560\uAE4C\uC694?"));
|
|
2119
2238
|
const serverKeys = content.command("server-keys").description("\uCF58\uD150\uCE20 \uC11C\uBC84 \uD0A4 \uC870\uD68C\xB7\uD3D0\uAE30 (platform:read\xB7platform:write, \uBC1C\uAE09\xB7\uD68C\uC804\uC740 \uD06C\uB9AC\uC5D0\uC774\uD130 \uC13C\uD130)");
|
|
2120
|
-
runtime2.action(serverKeys.command("list <id>"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/server-keys`)));
|
|
2239
|
+
runtime2.action(serverKeys.command("list <id>").description("\uC11C\uBC84 \uD0A4 \uBAA9\uB85D (platform:read)"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/server-keys`)));
|
|
2121
2240
|
for (const name2 of ["issue", "rotate"]) {
|
|
2122
2241
|
runtime2.action(serverKeys.command(name2).description("\uD06C\uB9AC\uC5D0\uC774\uD130 \uC13C\uD130 \uC804\uC6A9 \uC548\uB0B4").argument("[args...]").allowUnknownOption(), async () => {
|
|
2123
2242
|
throw new CliError(SERVER_KEY_CENTER_ONLY, "SERVER_KEY_CENTER_ONLY", 403);
|
|
2124
2243
|
});
|
|
2125
2244
|
}
|
|
2126
|
-
runtime2.action(serverKeys.command("revoke <id> <key-id>"), async (ctx, [id2, keyIdValue]) => mutate(ctx, "DELETE", `/v4/creator/contents/${uuid(id2)}/server-keys/${keyId(keyIdValue)}`, void 0, "\uC774 \uC11C\uBC84 \uD0A4\uB97C \uC989\uC2DC \uD3D0\uAE30\uD560\uAE4C\uC694?"));
|
|
2245
|
+
runtime2.action(serverKeys.command("revoke <id> <key-id>").description("\uC11C\uBC84 \uD0A4 \uC989\uC2DC \uD3D0\uAE30 (platform:write)"), async (ctx, [id2, keyIdValue]) => mutate(ctx, "DELETE", `/v4/creator/contents/${uuid(id2)}/server-keys/${keyId(keyIdValue)}`, void 0, "\uC774 \uC11C\uBC84 \uD0A4\uB97C \uC989\uC2DC \uD3D0\uAE30\uD560\uAE4C\uC694?"));
|
|
2127
2246
|
const shared = content.command("shared").description("\uACF5\uC720 \uBB38\uC11C \uC870\uD68C\xB7\uC228\uAE40\xB7\uC0AD\uC81C (platform:read\xB7platform:write)");
|
|
2128
|
-
runtime2.action(shared.command("collections <id>"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/shared-document-collections`)));
|
|
2129
|
-
runtime2.action(shared.command("list <id> <collection>").option("--limit <count>", "1~100\uAC1C, \uAE30\uBCF8 50"), async (ctx, [id2, collectionValue], opts) => ctx.output(await ctx.api.request(
|
|
2247
|
+
runtime2.action(shared.command("collections <id>").description("\uACF5\uC720 \uBB38\uC11C \uCEEC\uB809\uC158 \uC774\uB984 (platform:read)"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/shared-document-collections`)));
|
|
2248
|
+
runtime2.action(shared.command("list <id> <collection>").description("\uACF5\uC720 \uBB38\uC11C \uBAA9\uB85D (platform:read)").option("--limit <count>", "1~100\uAC1C, \uAE30\uBCF8 50"), async (ctx, [id2, collectionValue], opts) => ctx.output(await ctx.api.request(
|
|
2130
2249
|
"GET",
|
|
2131
2250
|
`/v4/creator/contents/${uuid(id2)}/shared-documents/${parse(collection, collectionValue)}`,
|
|
2132
2251
|
{ query: { limit: opts.limit } }
|
|
2133
2252
|
)));
|
|
2134
|
-
runtime2.action(shared.command("get <id> <collection> <key>"), async (ctx, [id2, collectionValue, keyValue]) => ctx.output(await ctx.api.request(
|
|
2253
|
+
runtime2.action(shared.command("get <id> <collection> <key>").description("\uACF5\uC720 \uBB38\uC11C \uD558\uB098 \uC870\uD68C (platform:read)"), async (ctx, [id2, collectionValue, keyValue]) => ctx.output(await ctx.api.request(
|
|
2135
2254
|
"GET",
|
|
2136
2255
|
`/v4/creator/contents/${uuid(id2)}/shared-documents/${parse(collection, collectionValue)}/${parse(documentKey, keyValue)}`
|
|
2137
2256
|
)));
|
|
2138
|
-
runtime2.action(shared.command("hide <id> <collection> <key>"), async (ctx, [id2, collectionValue, keyValue]) => mutate(
|
|
2257
|
+
runtime2.action(shared.command("hide <id> <collection> <key>").description("\uACF5\uC720 \uBB38\uC11C \uC228\uAE40 (platform:write)"), async (ctx, [id2, collectionValue, keyValue]) => mutate(
|
|
2139
2258
|
ctx,
|
|
2140
2259
|
"POST",
|
|
2141
2260
|
`/v4/creator/contents/${uuid(id2)}/shared-documents/${parse(collection, collectionValue)}/${parse(documentKey, keyValue)}/hide`,
|
|
2142
2261
|
void 0,
|
|
2143
2262
|
"\uC774 \uACF5\uC720 \uBB38\uC11C\uB97C \uC228\uAE38\uAE4C\uC694? \uBAA9\uB85D\xB7\uC21C\uC704\uC5D0\uC11C \uC81C\uC678\uB418\uBA70 \uBB38\uC11C \uC790\uCCB4\uB294 \uB0A8\uC2B5\uB2C8\uB2E4."
|
|
2144
2263
|
));
|
|
2145
|
-
runtime2.action(shared.command("delete <id> <collection> <key>"), async (ctx, [id2, collectionValue, keyValue]) => mutate(
|
|
2264
|
+
runtime2.action(shared.command("delete <id> <collection> <key>").description("\uACF5\uC720 \uBB38\uC11C \uC601\uAD6C \uC0AD\uC81C (platform:write)"), async (ctx, [id2, collectionValue, keyValue]) => mutate(
|
|
2146
2265
|
ctx,
|
|
2147
2266
|
"DELETE",
|
|
2148
2267
|
`/v4/creator/contents/${uuid(id2)}/shared-documents/${parse(collection, collectionValue)}/${parse(documentKey, keyValue)}`,
|
|
@@ -4061,7 +4180,12 @@ function serverData(schema, value2) {
|
|
|
4061
4180
|
if (!parsed.success) throw new CliError("\uC2A4\uD0AC API \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 502);
|
|
4062
4181
|
return parsed.data;
|
|
4063
4182
|
}
|
|
4064
|
-
|
|
4183
|
+
var PAT_FOR_WRITE = "\uC2A4\uD0AC \uC11C\uBC84 \uAC80\uC99D\xB7\uC5C5\uB85C\uB4DC\xB7\uC644\uB8CC \uCC98\uB9AC\uC5D0\uB294 skill:write \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
|
|
4184
|
+
var PAT_FOR_SUBMIT = "\uC2A4\uD0AC \uC2EC\uC0AC \uC81C\uCD9C\uC5D0\uB294 skill:write\uC640 skill:publish \uAD8C\uD55C(\uB458 \uB2E4)\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
|
|
4185
|
+
var PAT_FOR_CANCEL = "\uC2A4\uD0AC \uC5C5\uB85C\uB4DC\xB7\uC2EC\uC0AC \uCDE8\uC18C\uC5D0\uB294 skill:write \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
|
|
4186
|
+
var PAT_FOR_RELEASE = "\uC2A4\uD0AC \uAC8C\uC2DC\uC5D0\uB294 skill:publish \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
|
|
4187
|
+
var PAT_FOR_DEPRECATE = "\uC2A4\uD0AC \uC9C0\uC6D0 \uC885\uB8CC\uC5D0\uB294 skill:publish \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
|
|
4188
|
+
function ensurePat(client, message = PAT_FOR_WRITE) {
|
|
4065
4189
|
const token = client.settings.token;
|
|
4066
4190
|
if (!token || !/^pat_[a-f0-9]{64}$/.test(token)) {
|
|
4067
4191
|
throw new CliError(message, "SKILL_PAT_REQUIRED", 401);
|
|
@@ -4146,8 +4270,8 @@ async function pushSkillVersion(client, prepared, skillId) {
|
|
|
4146
4270
|
}
|
|
4147
4271
|
}
|
|
4148
4272
|
function registerSkillCommands(program2, runtime2) {
|
|
4149
|
-
const skill = program2.command("skill").description("\uC2A4\uD0AC \uD328\uD0A4\uC9C0 \uAC80\uC0AC\xB7\uC5C5\uB85C\uB4DC");
|
|
4150
|
-
runtime2.action(skill.command("list").option("-q, --query <text>", "\uC2A4\uD0AC \uC774\uB984 \uAC80\uC0C9").option("--category <category>", "\uBD84\uB958").option("--entity <entity>", "\uC0B0\uCD9C\uBB3C \uC5D4\uD2F0\uD2F0(character)").option("--type <type>", "\uC2A4\uD0AC \uC720\uD615(template|instruction|tool)").option("--official <value>", "\uACF5\uC2DD \uC2A4\uD0AC\uB9CC true \uB610\uB294 false").option("--cursor <id>", "\uB2E4\uC74C \uD398\uC774\uC9C0 UUID").option("--all", "\uBAA8\uB4E0 \uD398\uC774\uC9C0 \uC870\uD68C"), async (ctx, _args, opts) => {
|
|
4273
|
+
const skill = program2.command("skill").description("\uC2A4\uD0AC \uD328\uD0A4\uC9C0 \uAC80\uC0AC\xB7\uC5C5\uB85C\uB4DC\xB7\uC2EC\uC0AC\xB7\uAC8C\uC2DC (skill:read\xB7write\xB7publish)");
|
|
4274
|
+
runtime2.action(skill.command("list").description("\uC2A4\uD0AC \uBAA9\uB85D \uAC80\uC0C9 (skill:read)").option("-q, --query <text>", "\uC2A4\uD0AC \uC774\uB984 \uAC80\uC0C9").option("--category <category>", "\uBD84\uB958").option("--entity <entity>", "\uC0B0\uCD9C\uBB3C \uC5D4\uD2F0\uD2F0(character)").option("--type <type>", "\uC2A4\uD0AC \uC720\uD615(template|instruction|tool)").option("--official <value>", "\uACF5\uC2DD \uC2A4\uD0AC\uB9CC true \uB610\uB294 false").option("--cursor <id>", "\uB2E4\uC74C \uD398\uC774\uC9C0 UUID").option("--all", "\uBAA8\uB4E0 \uD398\uC774\uC9C0 \uC870\uD68C"), async (ctx, _args, opts) => {
|
|
4151
4275
|
const query = opts.query === void 0 ? void 0 : z9.string().trim().min(1).max(100).safeParse(opts.query);
|
|
4152
4276
|
if (query && !query.success) throw new CliError("\uAC80\uC0C9\uC5B4\uB294 1~100\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
4153
4277
|
const selectedCategory = opts.category === void 0 ? void 0 : category.safeParse(opts.category);
|
|
@@ -4189,22 +4313,22 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4189
4313
|
cursor = next;
|
|
4190
4314
|
}
|
|
4191
4315
|
});
|
|
4192
|
-
runtime2.action(skill.command("get <slug>"), async (ctx, [value2]) => {
|
|
4316
|
+
runtime2.action(skill.command("get <slug>").description("\uC2A4\uD0AC \uC0C1\uC138 \uC870\uD68C (skill:read)"), async (ctx, [value2]) => {
|
|
4193
4317
|
const parsed = skillSlug.safeParse(value2);
|
|
4194
4318
|
if (!parsed.success) throw new CliError("\uC2A4\uD0AC \uC774\uB984\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
4195
4319
|
ctx.output(await ctx.api.request("GET", `/v4/skills/${parsed.data}`));
|
|
4196
4320
|
});
|
|
4197
|
-
runtime2.action(skill.command("form <slug> <version>"), async (ctx, [value2, version]) => {
|
|
4321
|
+
runtime2.action(skill.command("form <slug> <version>").description("\uC2A4\uD0AC \uBC84\uC804 \uC785\uB825 \uC591\uC2DD \uC870\uD68C (skill:read)"), async (ctx, [value2, version]) => {
|
|
4198
4322
|
const parsed = skillSlug.safeParse(value2);
|
|
4199
4323
|
const parsedVersion = semver.safeParse(version);
|
|
4200
4324
|
if (!parsed.success || !parsedVersion.success) throw new CliError("\uC2A4\uD0AC \uC774\uB984\uACFC \uBC84\uC804\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
4201
4325
|
ctx.output(await ctx.api.request("GET", `/v4/skills/${parsed.data}/versions/${parsedVersion.data}/form`));
|
|
4202
4326
|
});
|
|
4203
|
-
runtime2.action(skill.command("status <skill-id> <version-id>"), async (ctx, [skillId, versionId]) => {
|
|
4327
|
+
runtime2.action(skill.command("status <skill-id> <version-id>").description("\uB0B4 \uC2A4\uD0AC \uBC84\uC804\uC758 \uC2EC\uC0AC\xB7\uAC8C\uC2DC \uC0C1\uD0DC \uC870\uD68C (skill:read)"), async (ctx, [skillId, versionId]) => {
|
|
4204
4328
|
const ids = skillVersionIds(skillId, versionId);
|
|
4205
4329
|
ctx.output(await ctx.api.request("GET", `/v4/creator/skills/${ids.skillId}/versions/${ids.versionId}`));
|
|
4206
4330
|
});
|
|
4207
|
-
runtime2.action(skill.command("validate [dir]").option("--remote", "\uC11C\uBC84\uC5D0\uC11C\uB3C4 \uD328\uD0A4\uC9C0\uB97C \uB4DC\uB77C\uC774\uB7F0 \uAC80\uC99D"), async (ctx, [dir], opts) => {
|
|
4331
|
+
runtime2.action(skill.command("validate [dir]").description("\uC2A4\uD0AC \uD328\uD0A4\uC9C0 \uB85C\uCEEC \uAC80\uC0AC (\uB85C\uADF8\uC778 \uBD88\uD544\uC694, --remote\uB294 skill:write)").option("--remote", "\uC11C\uBC84\uC5D0\uC11C\uB3C4 \uD328\uD0A4\uC9C0\uB97C \uB4DC\uB77C\uC774\uB7F0 \uAC80\uC99D (skill:write)"), async (ctx, [dir], opts) => {
|
|
4208
4332
|
const prepared = await prepareSkillPackage(dir ?? ".");
|
|
4209
4333
|
if (opts.remote && !ctx.options.dryRun) {
|
|
4210
4334
|
const remote = await validateSkillRemotely(ctx.api, prepared.bytes);
|
|
@@ -4225,7 +4349,7 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4225
4349
|
...opts.remote ? { remote_skipped: true } : {}
|
|
4226
4350
|
});
|
|
4227
4351
|
});
|
|
4228
|
-
runtime2.action(skill.command("push [dir]").option("--skill-id <id>", "\uAE30\uC874 \uC2A4\uD0AC ID\uC5D0 \uC0C8 \uBC84\uC804 \uCD94\uAC00"), async (ctx, [dir], opts) => {
|
|
4352
|
+
runtime2.action(skill.command("push [dir]").description("\uC2A4\uD0AC \uD328\uD0A4\uC9C0 \uAC80\uC99D \uD6C4 \uC0C8 \uBC84\uC804 \uC5C5\uB85C\uB4DC (skill:write)").option("--skill-id <id>", "\uAE30\uC874 \uC2A4\uD0AC ID\uC5D0 \uC0C8 \uBC84\uC804 \uCD94\uAC00"), async (ctx, [dir], opts) => {
|
|
4229
4353
|
const prepared = await prepareSkillPackage(dir ?? ".");
|
|
4230
4354
|
if (prepared.manifest.type !== "instruction" && prepared.manifest.type !== "template") {
|
|
4231
4355
|
throw new CliError("\uD604\uC7AC \uC2A4\uD0AC \uC5C5\uB85C\uB4DC\uB294 instruction\xB7template \uC720\uD615\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.", "SKILL_TYPE_UNSUPPORTED");
|
|
@@ -4247,7 +4371,7 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4247
4371
|
}
|
|
4248
4372
|
ctx.output(await pushSkillVersion(ctx.api, prepared, skillIdOption?.data));
|
|
4249
4373
|
});
|
|
4250
|
-
runtime2.action(skill.command("complete <skill-id> <version-id>"), async (ctx, [skillId, versionId]) => {
|
|
4374
|
+
runtime2.action(skill.command("complete <skill-id> <version-id>").description("\uC5C5\uB85C\uB4DC\uD55C \uC2A4\uD0AC \uBC84\uC804 \uC644\uB8CC \uCC98\uB9AC \uC7AC\uC2DC\uB3C4 (skill:write)"), async (ctx, [skillId, versionId]) => {
|
|
4251
4375
|
ensurePat(ctx.api);
|
|
4252
4376
|
const parsedSkillId = uuid2.safeParse(skillId);
|
|
4253
4377
|
const parsedVersionId = uuid2.safeParse(versionId);
|
|
@@ -4258,8 +4382,8 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4258
4382
|
}
|
|
4259
4383
|
ctx.output(await ctx.api.request("POST", `/v4/creator/skills/${parsedSkillId.data}/versions/${parsedVersionId.data}/complete`));
|
|
4260
4384
|
});
|
|
4261
|
-
runtime2.action(skill.command("submit <skill-id> <version-id>"), async (ctx, [skillId, versionId]) => {
|
|
4262
|
-
ensurePat(ctx.api,
|
|
4385
|
+
runtime2.action(skill.command("submit <skill-id> <version-id>").description("\uAC80\uC99D \uC644\uB8CC\uD55C \uC2A4\uD0AC \uBC84\uC804 \uC2EC\uC0AC \uC81C\uCD9C (skill:write\uC640 skill:publish \uB458 \uB2E4 \uD544\uC694)"), async (ctx, [skillId, versionId]) => {
|
|
4386
|
+
ensurePat(ctx.api, PAT_FOR_SUBMIT);
|
|
4263
4387
|
const ids = skillVersionIds(skillId, versionId);
|
|
4264
4388
|
const path = `/v4/creator/skills/${ids.skillId}/versions/${ids.versionId}`;
|
|
4265
4389
|
const current = serverData(managedVersionSchema, (await ctx.api.request("GET", path)).data);
|
|
@@ -4279,8 +4403,8 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4279
4403
|
await ctx.confirm(`${current.version} \uBC84\uC804(\uD328\uD0A4\uC9C0 ${current.package_hash})\uC744 \uC2EC\uC0AC\uC5D0 \uC81C\uCD9C\uD560\uAE4C\uC694?`);
|
|
4280
4404
|
ctx.output(await ctx.api.request("POST", `${path}/submit`));
|
|
4281
4405
|
});
|
|
4282
|
-
runtime2.action(skill.command("deprecate <skill-id>").description("\uC2A4\uD0AC\uC744 \uC9C0\uC6D0 \uC885\uB8CC\uD55C\uB2E4 (\uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC74C, \uC0C8 \uC124\uCE58\xB7\uC0C8 \uC81C\uC791 \uBD88\uAC00)"), async (ctx, [skillId]) => {
|
|
4283
|
-
ensurePat(ctx.api);
|
|
4406
|
+
runtime2.action(skill.command("deprecate <skill-id>").description("\uC2A4\uD0AC\uC744 \uC9C0\uC6D0 \uC885\uB8CC\uD55C\uB2E4 (\uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC74C, \uC0C8 \uC124\uCE58\xB7\uC0C8 \uC81C\uC791 \uBD88\uAC00, skill:publish)"), async (ctx, [skillId]) => {
|
|
4407
|
+
ensurePat(ctx.api, PAT_FOR_DEPRECATE);
|
|
4284
4408
|
const parsedSkillId = uuid2.safeParse(skillId);
|
|
4285
4409
|
if (!parsedSkillId.success) throw new CliError("\uC2A4\uD0AC ID\uB294 UUID\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
4286
4410
|
const path = `/v4/creator/skills/${parsedSkillId.data}/deprecate`;
|
|
@@ -4291,8 +4415,8 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4291
4415
|
await ctx.confirm("\uC774 \uC2A4\uD0AC\uC744 \uC9C0\uC6D0 \uC885\uB8CC\uD560\uAE4C\uC694? \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC73C\uBA70 \uC0C8 \uC124\uCE58\xB7\uC0C8 \uC81C\uC791\uC774 \uBD88\uAC00\uB2A5\uD569\uB2C8\uB2E4(\uAE30\uC874 \uCF58\uD150\uCE20\uC758 \uACE0\uC815 \uBC84\uC804\uC740 \uC720\uC9C0\uB429\uB2C8\uB2E4).");
|
|
4292
4416
|
ctx.output(await ctx.api.request("POST", path));
|
|
4293
4417
|
});
|
|
4294
|
-
runtime2.action(skill.command("cancel <skill-id> <version-id>"), async (ctx, [skillId, versionId]) => {
|
|
4295
|
-
ensurePat(ctx.api);
|
|
4418
|
+
runtime2.action(skill.command("cancel <skill-id> <version-id>").description("\uC2A4\uD0AC \uBC84\uC804\uC758 \uC5C5\uB85C\uB4DC \uB610\uB294 \uC2EC\uC0AC \uCDE8\uC18C (skill:write)"), async (ctx, [skillId, versionId]) => {
|
|
4419
|
+
ensurePat(ctx.api, PAT_FOR_CANCEL);
|
|
4296
4420
|
const ids = skillVersionIds(skillId, versionId);
|
|
4297
4421
|
const path = `/v4/creator/skills/${ids.skillId}/versions/${ids.versionId}`;
|
|
4298
4422
|
if (ctx.options.dryRun) {
|
|
@@ -4303,9 +4427,9 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4303
4427
|
ctx.output(await ctx.api.request("POST", `${path}/cancel`));
|
|
4304
4428
|
});
|
|
4305
4429
|
runtime2.action(
|
|
4306
|
-
skill.command("release <skill-id> <version-id>").requiredOption("--visibility <value>", "private|unlisted|public"),
|
|
4430
|
+
skill.command("release <skill-id> <version-id>").description("\uC2B9\uC778\uB41C \uC2A4\uD0AC \uBC84\uC804\uC744 \uACF5\uAC1C \uBC94\uC704\uB85C \uAC8C\uC2DC (skill:publish)").requiredOption("--visibility <value>", "private|unlisted|public"),
|
|
4307
4431
|
async (ctx, [skillId, versionId], opts) => {
|
|
4308
|
-
ensurePat(ctx.api);
|
|
4432
|
+
ensurePat(ctx.api, PAT_FOR_RELEASE);
|
|
4309
4433
|
const ids = skillVersionIds(skillId, versionId);
|
|
4310
4434
|
const selected = visibility.safeParse(opts.visibility);
|
|
4311
4435
|
if (!selected.success) throw new CliError("\uACF5\uAC1C \uBC94\uC704\uB294 private, unlisted, public \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
@@ -4387,7 +4511,7 @@ function parsePat(schema, input, label) {
|
|
|
4387
4511
|
return parsed.data;
|
|
4388
4512
|
}
|
|
4389
4513
|
var toolDefinitions = [
|
|
4390
|
-
{ name: "platform_authoring", description: "\uACF5\uAC1C \uC2A4\uD0AC \uC81C\uC791 \uC138\uC158\xB7\uD3FC\xB7\uC790\uC0B0\xB7\uC774\uBBF8\uC9C0 \uC0DD\uC131\xB7\uD328\uD0A4\uC9D5\uC744 \uCC98\uB9AC\uD569\uB2C8\uB2E4. \uC774\uBBF8\uC9C0 \uC0DD\uC131\uC5D0\uB294 \uACAC\uC801 \uC2B9\uC778 \uAC00\uACA9\uACFC \uBA71\uB4F1\uC131 \uD0A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", inputSchema: { type: "object", ...z10.toJSONSchema(authoringInput) }, annotations: { readOnlyHint: false, destructiveHint: false } },
|
|
4514
|
+
{ name: "platform_authoring", description: "\uACF5\uAC1C \uC2A4\uD0AC \uC81C\uC791 \uC138\uC158\xB7\uD3FC\xB7\uC790\uC0B0\xB7\uC774\uBBF8\uC9C0 \uC0DD\uC131\xB7\uD328\uD0A4\uC9D5\uC744 \uCC98\uB9AC\uD569\uB2C8\uB2E4. \uC774\uBBF8\uC9C0 \uC0DD\uC131\uC5D0\uB294 \uACAC\uC801 \uC2B9\uC778 \uAC00\uACA9\uACFC \uBA71\uB4F1\uC131 \uD0A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. creator-content:write \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", inputSchema: { type: "object", ...z10.toJSONSchema(authoringInput) }, annotations: { readOnlyHint: false, destructiveHint: false } },
|
|
4391
4515
|
{
|
|
4392
4516
|
name: "platform_usage_get",
|
|
4393
4517
|
description: "\uC11C\uBC84 \uD0A4\uAC00 \uC18D\uD55C \uCF58\uD150\uCE20\uC758 \uD50C\uB7AB\uD3FC \uC0AC\uC6A9\uB7C9\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
|
|
@@ -4433,13 +4557,13 @@ var toolDefinitions = [
|
|
|
4433
4557
|
// 아래는 개인 액세스 토큰(CLACK_TOKEN, pat_)으로 동작하는 개인 도구다. 서버 키 도구와 인증 평면이 다르다.
|
|
4434
4558
|
{
|
|
4435
4559
|
name: "platform_skill_list",
|
|
4436
|
-
description: "\uACF5\uAC1C \uC2A4\uD0AC \uBAA9\uB85D\uC744 \uC774\uB984\uC73C\uB85C \uAC80\uC0C9\uD569\uB2C8\uB2E4.",
|
|
4560
|
+
description: "\uACF5\uAC1C \uC2A4\uD0AC \uBAA9\uB85D\uC744 \uC774\uB984\uC73C\uB85C \uAC80\uC0C9\uD569\uB2C8\uB2E4. skill:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
4437
4561
|
inputSchema: z10.toJSONSchema(patSchemas.skillList),
|
|
4438
4562
|
annotations: { readOnlyHint: true, destructiveHint: false }
|
|
4439
4563
|
},
|
|
4440
4564
|
{
|
|
4441
4565
|
name: "platform_skill_get",
|
|
4442
|
-
description: "\uC2A4\uD0AC \uC0C1\uC138\uB97C \uC774\uB984(slug)\uC73C\uB85C \uC870\uD68C\uD569\uB2C8\uB2E4.",
|
|
4566
|
+
description: "\uC2A4\uD0AC \uC0C1\uC138\uB97C \uC774\uB984(slug)\uC73C\uB85C \uC870\uD68C\uD569\uB2C8\uB2E4. skill:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
4443
4567
|
inputSchema: z10.toJSONSchema(patSchemas.skillGet),
|
|
4444
4568
|
annotations: { readOnlyHint: true, destructiveHint: false }
|
|
4445
4569
|
},
|
|
@@ -4451,13 +4575,13 @@ var toolDefinitions = [
|
|
|
4451
4575
|
},
|
|
4452
4576
|
{
|
|
4453
4577
|
name: "platform_skill_status",
|
|
4454
|
-
description: "\uB0B4 \uC2A4\uD0AC \uBC84\uC804\uC758 \uC2EC\uC0AC\xB7\uAC8C\uC2DC \uC0C1\uD0DC\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.",
|
|
4578
|
+
description: "\uB0B4 \uC2A4\uD0AC \uBC84\uC804\uC758 \uC2EC\uC0AC\xB7\uAC8C\uC2DC \uC0C1\uD0DC\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. skill:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
4455
4579
|
inputSchema: z10.toJSONSchema(patSchemas.skillVersionRef),
|
|
4456
4580
|
annotations: { readOnlyHint: true, destructiveHint: false }
|
|
4457
4581
|
},
|
|
4458
4582
|
{
|
|
4459
4583
|
name: "platform_skill_submit",
|
|
4460
|
-
description: "\uAC80\uC99D \uC644\uB8CC\uD55C \uC2A4\uD0AC \uBC84\uC804\uC744 \uC2EC\uC0AC\uC5D0 \uC81C\uCD9C\uD569\uB2C8\uB2E4. skill:write\
|
|
4584
|
+
description: "\uAC80\uC99D \uC644\uB8CC\uD55C \uC2A4\uD0AC \uBC84\uC804\uC744 \uC2EC\uC0AC\uC5D0 \uC81C\uCD9C\uD569\uB2C8\uB2E4. skill:write\uC640 skill:publish \uAD8C\uD55C(\uB458 \uB2E4)\uACFC confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
4461
4585
|
inputSchema: z10.toJSONSchema(patSchemas.skillSubmit),
|
|
4462
4586
|
annotations: { readOnlyHint: false, destructiveHint: true }
|
|
4463
4587
|
},
|
|
@@ -4574,7 +4698,7 @@ var PlatformMcpServer = class {
|
|
|
4574
4698
|
return { jsonrpc: "2.0", id: request.id, result: toolResult({ error: failure }, true) };
|
|
4575
4699
|
}
|
|
4576
4700
|
}
|
|
4577
|
-
/** 개인 액세스 토큰(platform:read·platform:write
|
|
4701
|
+
/** 개인 액세스 토큰(platform:read·platform:write, skill:*, creator-content:write·publish 스코프) 기반 도구. */
|
|
4578
4702
|
async callPatTool(name2, args) {
|
|
4579
4703
|
if (patDestructiveToolNames.has(name2) && (!isObject2(args) || args.confirm !== true)) {
|
|
4580
4704
|
return { preview: true, confirmation_required: true, tool: name2, arguments: redact(args) };
|
|
@@ -4655,6 +4779,19 @@ async function servePlatformMcp(settings, env = process.env) {
|
|
|
4655
4779
|
|
|
4656
4780
|
// src/commands/integrations.ts
|
|
4657
4781
|
var SKILLS_REPOSITORY = "https://github.com/clack-project/skills";
|
|
4782
|
+
var AGENT_SKILLS = [
|
|
4783
|
+
{ name: "clack-setup", audience: "general", minimum_cli_version: "0.1.1", cli_required: true },
|
|
4784
|
+
{ name: "clack-products", audience: "general", minimum_cli_version: "0.1.1", cli_required: true },
|
|
4785
|
+
{ name: "clack-content", audience: "general", minimum_cli_version: "0.1.1", cli_required: true },
|
|
4786
|
+
{ name: "clack-channel", audience: "general", minimum_cli_version: "0.1.1", cli_required: true },
|
|
4787
|
+
{ name: "clack-profile", audience: "general", minimum_cli_version: "0.1.1", cli_required: true },
|
|
4788
|
+
{ name: "clack-mcp", audience: "general", minimum_cli_version: "0.1.0", cli_required: false },
|
|
4789
|
+
{ name: "clack-creator-content", audience: "creator", minimum_cli_version: "0.1.1", cli_required: true },
|
|
4790
|
+
{ name: "clack-page", audience: "creator", minimum_cli_version: "0.1.1", cli_required: true },
|
|
4791
|
+
{ name: "clack-skill-package", audience: "creator", minimum_cli_version: "0.1.1", cli_required: true },
|
|
4792
|
+
{ name: "clack-platform-data", audience: "creator", minimum_cli_version: "0.1.1", cli_required: true }
|
|
4793
|
+
];
|
|
4794
|
+
var SKILLS_MINIMUM_CLI_VERSION = "0.1.1";
|
|
4658
4795
|
function quoteArgument(value2) {
|
|
4659
4796
|
return `'${value2.replaceAll("'", "'\\''")}'`;
|
|
4660
4797
|
}
|
|
@@ -4687,7 +4824,7 @@ function registerIntegrationCommands(program2, runtime2) {
|
|
|
4687
4824
|
const mcp = program2.command("mcp").description("MCP \uD074\uB77C\uC774\uC5B8\uD2B8 \uC5F0\uACB0\xB7\uD50C\uB7AB\uD3FC \uC870\uD68C \uB3C4\uAD6C");
|
|
4688
4825
|
runtime2.action(mcp.command("config").description("\uD1A0\uD070 \uC5C6\uB294 MCP \uC124\uC815 \uCD9C\uB825 (\uD30C\uC77C \uBCC0\uACBD\xB7\uC11C\uBC84 \uC694\uCCAD \uC5C6\uC74C)").option("--platform", "CLACK_SERVER_KEY\uB97C \uC0AC\uC6A9\uD558\uB294 \uB85C\uCEEC \uD50C\uB7AB\uD3FC \uC870\uD68C \uC11C\uBC84 \uC124\uC815").addOption(new Option2("--print", "Claude Code .mcp.json \uC870\uAC01 \uCD9C\uB825 (\uAE30\uBCF8)").conflicts(["claude", "codex"])).addOption(new Option2("--claude", "Claude Code \uB4F1\uB85D \uBA85\uB839 \uCD9C\uB825").conflicts(["print", "codex"])).addOption(new Option2("--codex", "Codex config.toml \uC870\uAC01 \uCD9C\uB825").conflicts(["print", "claude"])), async (ctx, _args, opts) => {
|
|
4689
4826
|
const config = opts.platform ? platformMcpConfiguration(ctx.api.settings.baseUrl) : mcpConfiguration(ctx.api.settings.baseUrl);
|
|
4690
|
-
const instructions = opts.platform ? "\uC5D0\uC774\uC804\uD2B8 \uC2E4\uD589 \uD658\uACBD\uC5D0 \uCF58\uD150\uCE20 \uC11C\uBC84 \uD0A4 \uB3C4\uAD6C\uB294 \uBC1C\uAE09\uBC1B\uC740 \uC11C\uBC84 \uD0A4\uB97C CLACK_SERVER_KEY\uB85C, \uC2A4\uD0AC\xB7\uCF58\uD150\uCE20 \uACF5\uAC1C\xB7\uACF5\uC720 \uBB38\uC11C \uB3C4\uAD6C\uB294 platform:read\xB7platform:write(\uD544\uC694 \uC2DC skill
|
|
4827
|
+
const instructions = opts.platform ? "\uC5D0\uC774\uC804\uD2B8 \uC2E4\uD589 \uD658\uACBD\uC5D0 \uCF58\uD150\uCE20 \uC11C\uBC84 \uD0A4 \uB3C4\uAD6C\uB294 \uBC1C\uAE09\uBC1B\uC740 \uC11C\uBC84 \uD0A4\uB97C CLACK_SERVER_KEY\uB85C, \uC2A4\uD0AC\xB7\uC81C\uC791\xB7\uCF58\uD150\uCE20 \uACF5\uAC1C\xB7\uACF5\uC720 \uBB38\uC11C \uB3C4\uAD6C\uB294 platform:read\xB7platform:write(\uD544\uC694 \uC2DC skill:*, \uC81C\uC791\uC740 creator-content:write, \uCF58\uD150\uCE20 \uACF5\uAC1C\uB294 creator-content:publish) \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC744 CLACK_TOKEN\uC73C\uB85C \uC124\uC815\uD558\uC138\uC694. \uD544\uC694\uD55C \uB3C4\uAD6C\uB9CC\uD07C\uB9CC \uC124\uC815\uD558\uBA74 \uB429\uB2C8\uB2E4. \uC124\uC815 \uCD9C\uB825\uC5D0\uB294 \uD0A4\xB7\uD1A0\uD070 \uC6D0\uBB38\uC744 \uB123\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uD658\uACBD\uACFC \uC124\uC815 \uC6D0\uC810\uC774 \uC77C\uCE58\uD558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694." : "\uC571\uC5D0\uC11C \uBC1C\uAE09\uD55C \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC744 \uC5D0\uC774\uC804\uD2B8 \uC2E4\uD589 \uD658\uACBD\uC758 CLACK_TOKEN\uC5D0 \uC548\uC804\uD558\uAC8C \uC124\uC815\uD558\uC138\uC694. CLI\uC5D0 \uC800\uC7A5\uB41C \uD1A0\uD070\uC740 \uC790\uB3D9 \uC804\uB2EC\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uD1A0\uD070\uC744 \uB300\uD654\xB7\uBA85\uB839 \uAE30\uB85D\xB7\uC124\uC815 \uD30C\uC77C\uC5D0 \uBD99\uC5EC \uB123\uC9C0 \uB9C8\uC138\uC694. CLI \uD504\uB85C\uD544\uC744 \uBC14\uAFB8\uC5B4\uB3C4 \uAE30\uC874 MCP \uC124\uC815\uC740 \uAC31\uC2E0\uB418\uC9C0 \uC54A\uC73C\uBBC0\uB85C URL\uACFC \uD1A0\uD070\uC758 \uACC4\uC815\uC744 \uD655\uC778\uD558\uC138\uC694.";
|
|
4691
4828
|
if (ctx.options.json) process.stdout.write(JSON.stringify({ ok: true, data: {
|
|
4692
4829
|
...config,
|
|
4693
4830
|
token_env: opts.platform ? "CLACK_SERVER_KEY" : "CLACK_TOKEN",
|
|
@@ -4705,11 +4842,14 @@ function registerIntegrationCommands(program2, runtime2) {
|
|
|
4705
4842
|
runtime2.action(program2.command("skills").description("\uC5D0\uC774\uC804\uD2B8 \uC2A4\uD0AC \uC124\uCE58 \uBC29\uBC95 \uC548\uB0B4"), async (ctx) => {
|
|
4706
4843
|
ctx.output({
|
|
4707
4844
|
repository: SKILLS_REPOSITORY,
|
|
4708
|
-
minimum_cli_version:
|
|
4845
|
+
minimum_cli_version: SKILLS_MINIMUM_CLI_VERSION,
|
|
4709
4846
|
cli_version: VERSION,
|
|
4847
|
+
skill_count: AGENT_SKILLS.length,
|
|
4848
|
+
skills: AGENT_SKILLS,
|
|
4710
4849
|
install: "npx skills add clack-project/skills",
|
|
4711
4850
|
install_global: "npx skills add clack-project/skills -g",
|
|
4712
4851
|
install_selected: "npx skills add clack-project/skills -s clack-products",
|
|
4852
|
+
install_selected_multiple: "npx skills add clack-project/skills --skill clack-setup clack-creator-content clack-page",
|
|
4713
4853
|
update: "npx skills update",
|
|
4714
4854
|
remove: "npx skills remove",
|
|
4715
4855
|
message: `\uACF5\uAC1C \uC800\uC7A5\uC18C ${SKILLS_REPOSITORY}\uC5D0\uC11C npx skills\uB85C \uC124\uCE58\uD569\uB2C8\uB2E4. \uC774 \uBA85\uB839\uC740 \uC548\uB0B4\uB9CC \uCD9C\uB825\uD558\uBA70 \uC124\uCE58\uB098 \uB85C\uADF8\uC778\uC744 \uC2E4\uD589\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`
|
|
@@ -4730,14 +4870,14 @@ var presentationSchema = z11.object({
|
|
|
4730
4870
|
}).strict();
|
|
4731
4871
|
function registerPageCommands(program2, runtime2) {
|
|
4732
4872
|
const page = program2.command("page").description("\uC138\uACC4\uAD00\xB7\uB0B4 \uACF5\uAC1C \uD648 HTML \uAFB8\uBBF8\uAE30 (custom-page:read\xB7write\xB7publish)");
|
|
4733
|
-
runtime2.action(page.command("list"), async (ctx) => ctx.output(await ctx.api.request("GET", base)));
|
|
4734
|
-
runtime2.action(page.command("status <id>"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `${base}/${uuid3(id2)}`)));
|
|
4735
|
-
runtime2.action(page.command("create").addOption(new Option3("--target <type>", "\uAFB8\uBC00 \uB300\uC0C1").choices(["profile", "space"]).makeOptionMandatory()).option("--space-id <uuid>", "\uC138\uACC4\uAD00 ID").requiredOption("--policy-version <version>", "\uB3D9\uC758\uD55C \uCF58\uD150\uCE20 \uC81C\uACF5 \uC815\uCC45 \uBC84\uC804"), async (ctx, _args, opts) => {
|
|
4873
|
+
runtime2.action(page.command("list").description("\uB0B4 \uD398\uC774\uC9C0 \uBAA9\uB85D (custom-page:read)"), async (ctx) => ctx.output(await ctx.api.request("GET", base)));
|
|
4874
|
+
runtime2.action(page.command("status <id>").description("\uD398\uC774\uC9C0\uC640 \uBC84\uC804\xB7\uC218\uC815 \uBC88\uD638 \uC0C1\uD0DC (custom-page:read)"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `${base}/${uuid3(id2)}`)));
|
|
4875
|
+
runtime2.action(page.command("create").description("\uAFB8\uBC00 \uD398\uC774\uC9C0 \uB4F1\uB85D (custom-page:write)").addOption(new Option3("--target <type>", "\uAFB8\uBC00 \uB300\uC0C1").choices(["profile", "space"]).makeOptionMandatory()).option("--space-id <uuid>", "\uC138\uACC4\uAD00 ID").requiredOption("--policy-version <version>", "\uB3D9\uC758\uD55C \uCF58\uD150\uCE20 \uC81C\uACF5 \uC815\uCC45 \uBC84\uC804"), async (ctx, _args, opts) => {
|
|
4736
4876
|
if (opts.target === "profile" && opts.spaceId) throw new CliError("\uB0B4 \uACF5\uAC1C \uD648\uC5D0\uB294 \uC138\uACC4\uAD00 ID\uB97C \uC9C0\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
4737
4877
|
const policy = parse(z11.literal("2026-09-22"), opts.policyVersion);
|
|
4738
4878
|
await mutate(ctx, "POST", base, { target_type: opts.target, ...opts.target === "space" ? { space_id: uuid3(opts.spaceId) } : {}, policy_version: policy });
|
|
4739
4879
|
});
|
|
4740
|
-
runtime2.action(page.command("upload <id> <file>").addOption(new Option3("--header <mode>", "\uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]).default("fixed")).addOption(new Option3("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).default("light")), async (ctx, [id2, file], opts) => {
|
|
4880
|
+
runtime2.action(page.command("upload <id> <file>").description("HTML/ZIP \uC5C5\uB85C\uB4DC\uB85C \uC0C8 \uBE44\uACF5\uAC1C \uBC84\uC804 \uC0DD\uC131 (custom-page:write)").addOption(new Option3("--header <mode>", "\uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]).default("fixed")).addOption(new Option3("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).default("light")), async (ctx, [id2, file], opts) => {
|
|
4741
4881
|
const path = `${base}/${uuid3(id2)}`;
|
|
4742
4882
|
const prepared = await prepareContentFile(file);
|
|
4743
4883
|
const presentation = parse(presentationSchema, { schema_version: 1, header_mode: opts.header, color_scheme: opts.color });
|
|
@@ -4754,16 +4894,16 @@ function registerPageCommands(program2, runtime2) {
|
|
|
4754
4894
|
throw failure;
|
|
4755
4895
|
}
|
|
4756
4896
|
});
|
|
4757
|
-
runtime2.action(page.command("complete <id> <upload-id>"), async (ctx, [id2, uploadId]) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/uploads/${uuid3(uploadId)}/complete`, {}));
|
|
4758
|
-
runtime2.action(page.command("presentation <id> <version-id>").addOption(new Option3("--header <mode>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]).makeOptionMandatory()).addOption(new Option3("--color <scheme>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).makeOptionMandatory()), async (ctx, [id2, versionId], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/presentation`, {
|
|
4897
|
+
runtime2.action(page.command("complete <id> <upload-id>").description("\uC5C5\uB85C\uB4DC \uC644\uB8CC \uCC98\uB9AC \uC7AC\uC2DC\uB3C4 (custom-page:write)"), async (ctx, [id2, uploadId]) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/uploads/${uuid3(uploadId)}/complete`, {}));
|
|
4898
|
+
runtime2.action(page.command("presentation <id> <version-id>").description("\uD30C\uC77C\uC740 \uB450\uACE0 \uD5E4\uB354 \uC124\uC815\uB9CC \uBC14\uAFBC \uC0C8 \uBC84\uC804 \uC0DD\uC131 (custom-page:write)").addOption(new Option3("--header <mode>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]).makeOptionMandatory()).addOption(new Option3("--color <scheme>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).makeOptionMandatory()), async (ctx, [id2, versionId], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/presentation`, {
|
|
4759
4899
|
presentation: parse(presentationSchema, { schema_version: 1, header_mode: opts.header, color_scheme: opts.color })
|
|
4760
4900
|
}));
|
|
4761
|
-
runtime2.action(page.command("preview <id> <version-id>"), async (ctx, [id2, versionId]) => ctx.output(await ctx.api.request("GET", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/preview`)));
|
|
4762
|
-
runtime2.action(page.command("submit <id> <version-id>"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/submit`, {}, "\uC571\uC5D0\uC11C \uD655\uC778\uD55C \uC774 \uBC84\uC804\uC758 \uC2EC\uC0AC\uB97C \uC694\uCCAD\uD560\uAE4C\uC694? \uC2B9\uC778 \uB4A4 \uC9C1\uC811 \uC801\uC6A9\uD574\uC57C \uD569\uB2C8\uB2E4."));
|
|
4901
|
+
runtime2.action(page.command("preview <id> <version-id>").description("\uC571 \uD655\uC778\uC6A9 \uBBF8\uB9AC\uBCF4\uAE30 \uB9C1\uD06C \uC870\uD68C (custom-page:read)"), async (ctx, [id2, versionId]) => ctx.output(await ctx.api.request("GET", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/preview`)));
|
|
4902
|
+
runtime2.action(page.command("submit <id> <version-id>").description("\uC571\uC5D0\uC11C \uD655\uC778 \uC644\uB8CC\uD55C \uBC84\uC804\uC758 \uC2EC\uC0AC \uC694\uCCAD (custom-page:write\uC640 custom-page:publish \uB458 \uB2E4 \uD544\uC694)"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/submit`, {}, "\uC571\uC5D0\uC11C \uD655\uC778\uD55C \uC774 \uBC84\uC804\uC758 \uC2EC\uC0AC\uB97C \uC694\uCCAD\uD560\uAE4C\uC694? \uC2B9\uC778 \uB4A4 \uC9C1\uC811 \uC801\uC6A9\uD574\uC57C \uD569\uB2C8\uB2E4."));
|
|
4763
4903
|
for (const name2 of ["apply", "restore"]) {
|
|
4764
|
-
runtime2.action(page.command(`${name2} <id> <version-id>`).requiredOption("--revision <number>", "status\uC5D0\uC11C \uD655\uC778\uD55C \uD604\uC7AC \uC218\uC815 \uBC88\uD638"), async (ctx, [id2, versionId], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/apply`, { version_id: uuid3(versionId), revision: revision3(opts.revision) }, "\uC2B9\uC778\uB41C \uC774 \uBC84\uC804\uC758 \uD30C\uC77C\uACFC \uD5E4\uB354 \uC124\uC815\uC744 \uC801\uC6A9\uD560\uAE4C\uC694?"));
|
|
4904
|
+
runtime2.action(page.command(`${name2} <id> <version-id>`).description(`${name2 === "apply" ? "\uC2B9\uC778\uB41C \uBC84\uC804 \uC801\uC6A9" : "\uC774\uC804 \uC2B9\uC778 \uBC84\uC804\uC73C\uB85C \uBCF5\uC6D0"} (custom-page:publish)`).requiredOption("--revision <number>", "status\uC5D0\uC11C \uD655\uC778\uD55C \uD604\uC7AC \uC218\uC815 \uBC88\uD638"), async (ctx, [id2, versionId], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/apply`, { version_id: uuid3(versionId), revision: revision3(opts.revision) }, "\uC2B9\uC778\uB41C \uC774 \uBC84\uC804\uC758 \uD30C\uC77C\uACFC \uD5E4\uB354 \uC124\uC815\uC744 \uC801\uC6A9\uD560\uAE4C\uC694?"));
|
|
4765
4905
|
}
|
|
4766
|
-
runtime2.action(page.command("disable <id>").requiredOption("--revision <number>", "status\uC5D0\uC11C \uD655\uC778\uD55C \uD604\uC7AC \uC218\uC815 \uBC88\uD638"), async (ctx, [id2], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/disable`, { revision: revision3(opts.revision) }, "\uAE30\uBCF8 \uD654\uBA74\uC73C\uB85C \uC804\uD658\uD560\uAE4C\uC694? \uC5C5\uB85C\uB4DC\uD55C \uBC84\uC804\uC740 \uBCF4\uC874\uB429\uB2C8\uB2E4."));
|
|
4906
|
+
runtime2.action(page.command("disable <id>").description("\uAE30\uBCF8 \uD654\uBA74\uC73C\uB85C \uC804\uD658 (custom-page:publish)").requiredOption("--revision <number>", "status\uC5D0\uC11C \uD655\uC778\uD55C \uD604\uC7AC \uC218\uC815 \uBC88\uD638"), async (ctx, [id2], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/disable`, { revision: revision3(opts.revision) }, "\uAE30\uBCF8 \uD654\uBA74\uC73C\uB85C \uC804\uD658\uD560\uAE4C\uC694? \uC5C5\uB85C\uB4DC\uD55C \uBC84\uC804\uC740 \uBCF4\uC874\uB429\uB2C8\uB2E4."));
|
|
4767
4907
|
}
|
|
4768
4908
|
|
|
4769
4909
|
// src/cli.ts
|