@milcho0604/velog-mcp 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.ko.md +366 -0
- package/README.md +381 -0
- package/dist/auth.d.ts +57 -0
- package/dist/auth.js +124 -0
- package/dist/auth.js.map +1 -0
- package/dist/capabilities.d.ts +50 -0
- package/dist/capabilities.js +60 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/client.d.ts +113 -0
- package/dist/client.js +322 -0
- package/dist/client.js.map +1 -0
- package/dist/format.d.ts +31 -0
- package/dist/format.js +66 -0
- package/dist/format.js.map +1 -0
- package/dist/graphql.d.ts +29 -0
- package/dist/graphql.js +82 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +149 -0
- package/dist/index.js.map +1 -0
- package/dist/me.d.ts +23 -0
- package/dist/me.js +35 -0
- package/dist/me.js.map +1 -0
- package/dist/ownership.d.ts +42 -0
- package/dist/ownership.js +62 -0
- package/dist/ownership.js.map +1 -0
- package/dist/plugin-env.d.ts +67 -0
- package/dist/plugin-env.js +102 -0
- package/dist/plugin-env.js.map +1 -0
- package/dist/ratelimit.d.ts +48 -0
- package/dist/ratelimit.js +79 -0
- package/dist/ratelimit.js.map +1 -0
- package/dist/render/chrome.d.ts +77 -0
- package/dist/render/chrome.js +287 -0
- package/dist/render/chrome.js.map +1 -0
- package/dist/render/cover.d.ts +29 -0
- package/dist/render/cover.js +195 -0
- package/dist/render/cover.js.map +1 -0
- package/dist/render/icons.d.ts +22 -0
- package/dist/render/icons.js +158 -0
- package/dist/render/icons.js.map +1 -0
- package/dist/render/index.d.ts +32 -0
- package/dist/render/index.js +137 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/page.d.ts +89 -0
- package/dist/render/page.js +761 -0
- package/dist/render/page.js.map +1 -0
- package/dist/render/tones.d.ts +30 -0
- package/dist/render/tones.js +46 -0
- package/dist/render/tones.js.map +1 -0
- package/dist/slug.d.ts +42 -0
- package/dist/slug.js +79 -0
- package/dist/slug.js.map +1 -0
- package/dist/tools/discover.d.ts +6 -0
- package/dist/tools/discover.js +106 -0
- package/dist/tools/discover.js.map +1 -0
- package/dist/tools/drafts.d.ts +23 -0
- package/dist/tools/drafts.js +227 -0
- package/dist/tools/drafts.js.map +1 -0
- package/dist/tools/export.d.ts +21 -0
- package/dist/tools/export.js +132 -0
- package/dist/tools/export.js.map +1 -0
- package/dist/tools/images.d.ts +34 -0
- package/dist/tools/images.js +556 -0
- package/dist/tools/images.js.map +1 -0
- package/dist/tools/posts.d.ts +14 -0
- package/dist/tools/posts.js +82 -0
- package/dist/tools/posts.js.map +1 -0
- package/dist/tools/profile-edit.d.ts +15 -0
- package/dist/tools/profile-edit.js +216 -0
- package/dist/tools/profile-edit.js.map +1 -0
- package/dist/tools/profile.d.ts +9 -0
- package/dist/tools/profile.js +133 -0
- package/dist/tools/profile.js.map +1 -0
- package/dist/tools/publish.d.ts +16 -0
- package/dist/tools/publish.js +424 -0
- package/dist/tools/publish.js.map +1 -0
- package/dist/tools/stats.d.ts +32 -0
- package/dist/tools/stats.js +154 -0
- package/dist/tools/stats.js.map +1 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/docs/PRD.md +146 -0
- package/docs/api-reference.md +329 -0
- package/docs/architecture.md +112 -0
- package/docs/decisions/0001-why-build-our-own.md +89 -0
- package/docs/decisions/0002-draft-only-write.md +84 -0
- package/docs/decisions/0003-token-env-only.md +109 -0
- package/docs/decisions/0004-capability-model.md +123 -0
- package/docs/decisions/0005-render-in-server.md +117 -0
- package/docs/decisions/0006-ship-as-plugin.md +532 -0
- package/docs/security.md +384 -0
- package/docs/tools.md +404 -0
- package/npm-shrinkwrap.json +2345 -0
- package/package.json +61 -0
package/docs/tools.md
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
# 도구 카탈로그
|
|
2
|
+
|
|
3
|
+
기본 21개 + 프로필 수정 5개(설정 시).
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
기본 (설정 없음) 읽기 + 초안 + 비공개 발행 + 그림 도구 21개
|
|
7
|
+
VELOG_ALLOW_PUBLIC=1 공개 발행 (파라미터만 추가)
|
|
8
|
+
VELOG_ALLOW_PROFILE=1 프로필·소개글·블로그제목·SNS·사진 도구 5개 추가
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
두 스위치는 **독립**이다. 프로필만 켜도 되고 발행만 켜도 된다.
|
|
12
|
+
|
|
13
|
+
설정이 꺼져 있으면 `is_private` 파라미터가 **어느 도구에도 없다.**
|
|
14
|
+
→ [ADR 0004](decisions/0004-capability-model.md)
|
|
15
|
+
|
|
16
|
+
| 도구 | 인증 | 성격 |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `velog_get_post` | — | 읽기 |
|
|
19
|
+
| `velog_list_posts` | — | 읽기 |
|
|
20
|
+
| `velog_search_posts` | — | 읽기 |
|
|
21
|
+
| `velog_trending_posts` | — | 읽기 |
|
|
22
|
+
| `velog_recent_posts` | — | 읽기 |
|
|
23
|
+
| `velog_get_user` | — | 읽기 |
|
|
24
|
+
| `velog_list_series` | — | 읽기 |
|
|
25
|
+
| `velog_user_tags` | — | 읽기 |
|
|
26
|
+
| `velog_blog_stats` | — | 읽기(집계) |
|
|
27
|
+
| `velog_export_posts` | — | 읽기 + 로컬 파일 쓰기 |
|
|
28
|
+
| `velog_whoami` | **필요** | 읽기 |
|
|
29
|
+
| `velog_list_drafts` | **필요** | 읽기 |
|
|
30
|
+
| `velog_create_draft` | **필요** | **쓰기 — 초안만** |
|
|
31
|
+
| `velog_update_draft` | **필요** | **쓰기 — 초안 전체 교체** (`destructive`) |
|
|
32
|
+
| `velog_publish_post` | **필요** | **쓰기 — 발행** (`destructive`) |
|
|
33
|
+
| `velog_publish_draft` | **필요** | **쓰기 — 초안을 발행** (`destructive`) |
|
|
34
|
+
| `velog_unpublish_post` | **필요** | **쓰기 — 초안으로 되돌림** (`destructive`) |
|
|
35
|
+
| `velog_update_post` | **필요** | **쓰기 — 발행글 수정** |
|
|
36
|
+
| `velog_render_diagram` | 올릴 때만 | 그림 생성 (+ 업로드) |
|
|
37
|
+
| `velog_render_cover` | 올릴 때만 | 표지 생성 (+ 업로드) |
|
|
38
|
+
| `velog_upload_image` | **필요** | **쓰기 — 공개 CDN 업로드** |
|
|
39
|
+
|
|
40
|
+
`username` 을 받는 도구 중 `velog_list_drafts`·`velog_blog_stats`·
|
|
41
|
+
`velog_export_posts`·`velog_search_posts` 는 **생략하면 토큰의 계정**을 쓴다.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 읽기
|
|
46
|
+
|
|
47
|
+
### `velog_get_post`
|
|
48
|
+
글 하나를 본문까지. `username` + `url_slug` 또는 `id`.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
https://velog.io/@velopert/react-context-tutorial
|
|
52
|
+
└ username ┘ └──── url_slug ────┘
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `velog_list_posts`
|
|
56
|
+
사용자의 글 목록(최신순). `tag` 로 좁힐 수 있다.
|
|
57
|
+
`cursor` 에 직전 응답 마지막 글의 `id` 를 주면 다음 페이지.
|
|
58
|
+
|
|
59
|
+
> `tag` 를 주면 벨로그가 요청 `limit` 을 무시하고 **20건 단위**로 준다(실측).
|
|
60
|
+
|
|
61
|
+
### `velog_search_posts`
|
|
62
|
+
키워드 검색. **`username` 을 함께 주면 그 사람 글 안에서만** 찾는다 —
|
|
63
|
+
"내가 예전에 쓴 그 글" 을 찾는 주 경로.
|
|
64
|
+
|
|
65
|
+
> 반환 건수가 `limit` 보다 적을 수 있다. 벨로그가 조회 후 일부를 걸러낸다.
|
|
66
|
+
> `count` 는 필터 이전 총계이므로 다음 페이지는 `offset + limit` 로 넘긴다.
|
|
67
|
+
|
|
68
|
+
### `velog_trending_posts`
|
|
69
|
+
`timeframe`: `day` | `week` | `month` | `year`
|
|
70
|
+
|
|
71
|
+
> `year` 는 벨로그가 `limit>20`·`offset>1000` 이면 **에러 없이 빈 결과**를 준다.
|
|
72
|
+
> 넘기기 전에 깎고, 깎았다는 사실을 응답에 알린다.
|
|
73
|
+
|
|
74
|
+
### `velog_recent_posts`
|
|
75
|
+
벨로그 전체 최신 글.
|
|
76
|
+
|
|
77
|
+
### `velog_whoami`
|
|
78
|
+
현재 토큰으로 인증된 계정. 토큰이 살아있는지 점검하는 용도로도 쓴다.
|
|
79
|
+
다른 도구가 `username` 을 생략했을 때 여기서 얻은 계정을 쓴다(프로세스당 1회 조회 후 캐시).
|
|
80
|
+
|
|
81
|
+
### `velog_get_user`
|
|
82
|
+
프로필·팔로워 수·블로그 제목·소개글.
|
|
83
|
+
|
|
84
|
+
### `velog_list_series`
|
|
85
|
+
연재 시리즈 목록과 각 시리즈의 글 수. **`id` 를 함께 낸다** —
|
|
86
|
+
`velog_create_draft` 의 `series_id` 에 넣는 값이다.
|
|
87
|
+
|
|
88
|
+
### `velog_user_tags`
|
|
89
|
+
사용자가 쓴 태그와 글 수. "이 사람이 뭘 주로 쓰나"를 요청 한 번으로 파악한다.
|
|
90
|
+
`velog_blog_stats` 는 글을 전부 훑으므로 무겁다 — 가벼운 질문엔 이쪽.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 파생 기능
|
|
95
|
+
|
|
96
|
+
### `velog_blog_stats`
|
|
97
|
+
벨로그에 없는 화면이라 직접 집계한다.
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
총 조회수 / 좋아요 / 댓글 / 글당 평균
|
|
101
|
+
조회수 상위 N편
|
|
102
|
+
연도별 분포
|
|
103
|
+
태그별 분포 (편수가 아니라 조회수 순)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`max_pages` 상한이 있다(1페이지 = 50편). 수집이 어떻게 끝났는지 **3가지로 구분**해
|
|
107
|
+
보고한다 — 이유에 따라 사용자가 할 일이 다르기 때문이다.
|
|
108
|
+
|
|
109
|
+
| 결과 | 뜻 | 할 일 |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| `complete` | 마지막 페이지까지 봤다 | 없음 |
|
|
112
|
+
| `page_limit` | `max_pages` 에 걸렸다 | `max_pages` 를 올린다 |
|
|
113
|
+
| `cursor_stalled` | 벨로그 커서가 안 움직였다 | 불완전함을 인지한다 |
|
|
114
|
+
|
|
115
|
+
`truncated` 불린 하나로 두면 커서 고착을 "다 봤다"로 오보고하게 된다.
|
|
116
|
+
|
|
117
|
+
### `velog_export_posts`
|
|
118
|
+
글을 YAML 프론트매터 + 마크다운으로 로컬에 저장한다.
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
---
|
|
122
|
+
title: "글 제목"
|
|
123
|
+
date: 2022-12-31T18:32:39.790Z
|
|
124
|
+
slug: "url-slug"
|
|
125
|
+
url: "https://velog.io/@username/url-slug"
|
|
126
|
+
tags: ["태그1", "태그2"]
|
|
127
|
+
likes: 260
|
|
128
|
+
views: 16323
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
본문…
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
글마다 상세를 받아오므로 시간이 걸린다(250ms 간격). 실패한 글은 건너뛰고
|
|
135
|
+
끝에 몇 편이 왜 실패했는지 보고한다.
|
|
136
|
+
|
|
137
|
+
> ⚠️ 같은 이름의 기존 파일은 **덮어쓴다.** 전용 디렉터리를 쓸 것.
|
|
138
|
+
> `destructiveHint: true` 로 표시돼 있다.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 쓰기 — 초안 전용
|
|
143
|
+
|
|
144
|
+
> **이 서버는 글을 발행할 수 없다.** `is_temp: true` 가 상수로 박혀 있고
|
|
145
|
+
> 도구 입력 스키마에 그 키가 없다. → [ADR 0002](decisions/0002-draft-only-write.md)
|
|
146
|
+
|
|
147
|
+
### `velog_create_draft`
|
|
148
|
+
|
|
149
|
+
> **5분에 5건까지만** 만들 수 있다. 벨로그가 최근 5분의 공개 글 10건 초과 시
|
|
150
|
+
> 그 시간대 글을 전부 비공개로 바꾸기 때문이다 — 초안도 계수에 포함된다.
|
|
151
|
+
> 막히면 이유와 해제 시각을 알려준다.
|
|
152
|
+
|
|
153
|
+
| 파라미터 | 필수 | 설명 |
|
|
154
|
+
| --- | --- | --- |
|
|
155
|
+
| `title` | ○ | 제목 |
|
|
156
|
+
| `body` | ○ | 본문 (마크다운) |
|
|
157
|
+
| `tags` | | 태그 배열. 기본 `[]` |
|
|
158
|
+
| `url_slug` | | 생략하면 제목에서 생성. 한글 그대로 둔다 |
|
|
159
|
+
| `thumbnail` | | 이미지 URL |
|
|
160
|
+
| `series_id` | | `velog_list_series` 에서 얻은 id. ★ 초안 생성 단계에서는 **벨로그가 무시한다** — `velog_update_draft` 를 한 번 더 불러야 실제로 붙는다 |
|
|
161
|
+
|
|
162
|
+
성공하면 편집 URL 과 함께 **"아직 발행되지 않았습니다"** 를 명시한다.
|
|
163
|
+
|
|
164
|
+
### `velog_update_draft`
|
|
165
|
+
|
|
166
|
+
`id` + 나머지는 `create_draft` 와 같다. **글 전체가 교체된다 — 부분 수정이 아니다.**
|
|
167
|
+
|
|
168
|
+
생략한 필드는 유지되지 않고 초기화된다:
|
|
169
|
+
|
|
170
|
+
| 생략하면 | 결과 |
|
|
171
|
+
| --- | --- |
|
|
172
|
+
| `tags` | 기존 태그가 **전부 삭제** |
|
|
173
|
+
| `url_slug` | 제목에서 새로 만들어 **주소가 바뀜** |
|
|
174
|
+
| `series_id` | 기존 **시리즈 연결이 끊김** |
|
|
175
|
+
|
|
176
|
+
그래서 `velog_get_post` 로 현재 값을 읽어 바꾸지 않을 필드도 그대로 다시
|
|
177
|
+
넘기는 편이 안전하다. `destructiveHint: true` 로 표시돼 있다.
|
|
178
|
+
|
|
179
|
+
> ⚠️ **이미 발행된 글의 `id` 를 주면 그 글이 임시저장으로 내려가 비공개가 된다.**
|
|
180
|
+
> `editPost` 는 상태를 덮어쓴다. 반드시 `velog_list_drafts` 로 확인한
|
|
181
|
+
> 초안 id 만 쓸 것.
|
|
182
|
+
|
|
183
|
+
### `velog_list_drafts`
|
|
184
|
+
내 임시저장 목록. 위 두 도구에 넣을 `id` 를 여기서 얻는다.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## 발행
|
|
191
|
+
|
|
192
|
+
> `VELOG_ALLOW_PUBLIC=1` 이 없으면 **비공개로만** 발행된다.
|
|
193
|
+
> 그 상태에서는 `is_private` 파라미터가 스키마에 존재하지 않는다.
|
|
194
|
+
|
|
195
|
+
### `velog_publish_post`
|
|
196
|
+
새 글을 바로 발행한다. 초안을 거치지 않는다.
|
|
197
|
+
파라미터는 `velog_create_draft` 와 같고, 설정이 켜져 있으면 `is_private` 이 추가된다
|
|
198
|
+
(기본 `true`).
|
|
199
|
+
|
|
200
|
+
### `velog_publish_draft`
|
|
201
|
+
기존 초안을 발행한다. **본문을 다시 넘길 필요가 없다** — 저장된 내용을 그대로 쓴다.
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
velog_publish_draft(id, is_private?)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
이렇게 만든 이유: 호출자가 본문을 다시 넘기게 하면 그 과정에서 태그·슬러그·시리즈가
|
|
208
|
+
날아간다(`editPost` 는 전체 교체다). 저장본을 읽어 그대로 실어 보내는 편이 안전하다.
|
|
209
|
+
|
|
210
|
+
발행된 글의 id 를 주면 거부한다.
|
|
211
|
+
|
|
212
|
+
### `velog_unpublish_post`
|
|
213
|
+
발행글을 임시저장으로 되돌린다. 글은 사라지지 않고 초안 목록으로 간다.
|
|
214
|
+
|
|
215
|
+
> ⚠️ **이미 나간 RSS·구독 메일은 회수되지 않는다.** 검색엔진 캐시도 한동안 남는다.
|
|
216
|
+
> 되돌린다는 건 '앞으로 안 보인다'는 뜻이지 '없던 일이 된다'는 뜻이 아니다.
|
|
217
|
+
|
|
218
|
+
이미 초안이면 아무것도 하지 않고 그렇다고 알린다.
|
|
219
|
+
|
|
220
|
+
### `velog_update_post`
|
|
221
|
+
발행된 글을 수정한다. **생략한 필드는 기존 값을 유지한다** — 초안 도구와 반대다.
|
|
222
|
+
|
|
223
|
+
| | `velog_update_draft` | `velog_update_post` |
|
|
224
|
+
| --- | --- | --- |
|
|
225
|
+
| 생략한 `tags` | 전부 삭제 | 유지 |
|
|
226
|
+
| 생략한 `url_slug` | 새로 생성 (주소 바뀜) | 유지 |
|
|
227
|
+
| 생략한 `series_id` | 연결 끊김 | 유지 |
|
|
228
|
+
| 생략한 `is_private` | — | **기존 공개 범위 유지** |
|
|
229
|
+
|
|
230
|
+
마지막 줄이 중요하다. 여기에 `default(true)` 를 걸어뒀다가 *공개글을 수정만 해도
|
|
231
|
+
비공개로 내려가는* 버그를 냈다. '만들 때'는 안전한 쪽이 기본이고, '고칠 때'는
|
|
232
|
+
**건드리지 않는 것**이 기본이다.
|
|
233
|
+
|
|
234
|
+
> 기본 설정(공개 발행 꺼짐)에서 공개 글을 수정하면 비공개로 내려간다.
|
|
235
|
+
> 공개 권한이 없는데 공개 상태를 유지시키면 그게 곧 공개 발행 권한이 되기 때문이다.
|
|
236
|
+
> 공개 글을 다루려면 `VELOG_ALLOW_PUBLIC=1` 을 켤 것.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 프로필 수정 (`VELOG_ALLOW_PROFILE=1`)
|
|
241
|
+
|
|
242
|
+
꺼져 있으면 **도구가 등록조차 되지 않는다** — 목록에 없으니 부를 수도 없다.
|
|
243
|
+
|
|
244
|
+
| 도구 | 바꾸는 것 |
|
|
245
|
+
| --- | --- |
|
|
246
|
+
| `velog_update_profile` | 표시 이름 · 한줄 소개 |
|
|
247
|
+
| `velog_update_about` | "소개" 탭의 긴 글 (전체 교체) |
|
|
248
|
+
| `velog_update_blog_title` | 블로그 제목 |
|
|
249
|
+
| `velog_update_social_links` | github · twitter · facebook · url · email |
|
|
250
|
+
| `velog_update_profile_image` | 프로필 사진 (http(s) URL) |
|
|
251
|
+
|
|
252
|
+
**왜 게이트가 있나** — 위험해서가 아니다. 전부 되돌릴 수 있고 본인 계정에만 영향이며
|
|
253
|
+
RSS·메일로 나가지도 않는다. 이유는 **혼동**이다: 프로필의 `short_bio` 와 글의
|
|
254
|
+
`short_description` 은 이름이 비슷하다. "소개 좀 고쳐줘" 가 어느 쪽인지 모호할 때,
|
|
255
|
+
스위치가 꺼져 있으면 모델이 프로필을 건드릴 수 없어 잘못 짚어도 사고가 안 난다.
|
|
256
|
+
|
|
257
|
+
> `velog_update_profile` 은 **생략한 항목을 유지**한다. 벨로그의
|
|
258
|
+
> `UpdateProfileInput` 은 `display_name` 과 `short_bio` 를 둘 다 필수로 받아서,
|
|
259
|
+
> 한쪽만 보내면 다른 쪽이 빈 문자열로 덮인다. 그래서 현재 값을 읽어 채워 보낸다.
|
|
260
|
+
> 실측 확인: 한줄소개만 바꿔도 이름이 그대로 남는다.
|
|
261
|
+
|
|
262
|
+
`velog_update_about` 은 **전체 교체**다. 덧붙이려면 `velog_get_user` 로 먼저 읽어
|
|
263
|
+
합친 뒤 넘겨야 한다.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## 구현하지 않은 것
|
|
268
|
+
|
|
269
|
+
좋아요·팔로우·댓글·계정 탈퇴·로그아웃·메일 발송·이메일 변경·알림 조작.
|
|
270
|
+
목록에서 뺀 게 아니라 **호출 코드를 쓰지 않았다.** 어떤 설정으로도 안 열린다.
|
|
271
|
+
|
|
272
|
+
글 삭제는 애초에 불가능하다 — v3 mutation 목록에 `deletePost` 가 없다.
|
|
273
|
+
|
|
274
|
+
전체 목록과 사유는 [security.md](security.md).
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 그림 도구 3종
|
|
280
|
+
|
|
281
|
+
### `velog_render_diagram`
|
|
282
|
+
|
|
283
|
+
구성도·흐름도를 그린다. **좌표는 사람이 정하고 나머지는 렌더러가 정한다.**
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
넘기는 것 nodes(x·y·제목·부제·아이콘·배지) / groups / edges / planes
|
|
287
|
+
정해지는 것 노드 폭·높이 · 캔버스 크기 · 선 꺾임과 라운딩 · 팬아웃 간격 · 라벨 자리
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- **폭·높이 생략 가능** — 글자를 `getBBox` 로 재서 정한다. 글자수 추정은 쓰지 않는다.
|
|
291
|
+
- **좌표 원점은 아무 데나** — 다 그린 뒤 내용 bbox 로 캔버스를 되맞춘다. 잘리지 않는다.
|
|
292
|
+
- **엣지는 `from`/`to` 만** — `"노드id:right"` 처럼 면을 고를 수도 있고, 생략하면
|
|
293
|
+
두 노드의 상대 위치로 정한다. 같은 면에서 여러 선이 나가면 등간격으로 벌린다.
|
|
294
|
+
꼭 손으로 꺾어야 하면 `points` 로 좌표를 직접 준다.
|
|
295
|
+
- **라벨 자리 자동** — 후보를 여러 개 만들어 카드·다른 라벨과 안 겹치는 자리를 고른다.
|
|
296
|
+
|
|
297
|
+
#### 자가감사 5종
|
|
298
|
+
|
|
299
|
+
| 항목 | 무엇을 잡나 |
|
|
300
|
+
| --- | --- |
|
|
301
|
+
| `over` | 글자가 카드 밖으로 나감 |
|
|
302
|
+
| `compressed` | 자간을 눌러 억지로 맞춤 → **노드 폭을 넓히라는 신호** |
|
|
303
|
+
| `cross` | 선이 노드를 관통하거나 노드 뒤로 숨음 |
|
|
304
|
+
| `overlap` | 서로 다른 선이 같은 자리에 겹침 |
|
|
305
|
+
| `collide` | 노드끼리 겹침 · 배지가 아이콘 침범 |
|
|
306
|
+
| `label` | 라벨이 카드 위나 다른 라벨 위에 얹힘 |
|
|
307
|
+
|
|
308
|
+
하나라도 걸리면 **올리지 않고** 무엇이 문제인지 돌려준다. 벨로그는 이미지 삭제 API 가
|
|
309
|
+
없어서 잘못 올린 건 지울 수 없다.
|
|
310
|
+
|
|
311
|
+
**이 판단을 끄는 파라미터는 없다.** 한때 `force_upload` 가 있었는데, 그건 이 저장소가
|
|
312
|
+
공개 발행에서 이미 배운 것(ADR 0004)을 그대로 어긴 것이었다 — 모델이 스스로 켤 수
|
|
313
|
+
있는 스위치는 방어가 아니다. 그래도 올려야 하면 `upload:false` 로 그린 뒤 PNG 를 보고
|
|
314
|
+
`velog_upload_image` 로 올린다.
|
|
315
|
+
|
|
316
|
+
#### 아이콘 28종
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
server database cache cloud browser mobile layers clock bell chart
|
|
320
|
+
lock key code file user gear alert check cross arrow branch package
|
|
321
|
+
terminal network mail search retry bolt
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
전부 도형 조합이다 — 밖에서 받아오는 게 없다. 색은 `icon_tone` 으로 고른다.
|
|
325
|
+
|
|
326
|
+
#### 입력 상한
|
|
327
|
+
|
|
328
|
+
그림 하나가 기기를 재우지 않도록 전부 묶여 있다.
|
|
329
|
+
|
|
330
|
+
| 항목 | 상한 |
|
|
331
|
+
| --- | --- |
|
|
332
|
+
| 좌표 (노드·그룹·`points`·`label_at`) | ±20,000 |
|
|
333
|
+
| 캔버스 | 6,000px · 면적 900만px — **페이지 안에서** 걸린다 |
|
|
334
|
+
| 노드 60 · 그룹 12 · 엣지 120 · 평면 6 · `members` 60 | 개수 |
|
|
335
|
+
| `points` | 40개/엣지 |
|
|
336
|
+
| 제목 200 · 부제 300 · 노드 제목·부제 120 · 태그 40 · id 64 | 글자 |
|
|
337
|
+
| 엣지 라벨 120 · 그룹 이름·부제 80 · 평면 이름 40 · `dash` 40 | 글자 |
|
|
338
|
+
|
|
339
|
+
`dash` 에 상한이 필요한 이유가 특이하다 — 이 값은 엣지 120개의 `stroke-dasharray` 로
|
|
340
|
+
**전부 복제되고** DOM 출력에도 그만큼 반복된다. 1MB 입력 하나가 100MB 넘는 DOM 이 된다.
|
|
341
|
+
|
|
342
|
+
#### 톤 10종
|
|
343
|
+
|
|
344
|
+
`slate gray blue green amber yellow purple teal rose indigo`
|
|
345
|
+
|
|
346
|
+
그룹 배경·아이콘·배지에 쓴다. 임의 색을 못 받게 한 이유는 두 가지다:
|
|
347
|
+
그림마다 톤이 달라지는 걸 막는 것과, 값이 결국 SVG 속성이 되므로 입력을 좁히는 것.
|
|
348
|
+
평면(`planes`) 색만 `#rrggbb` 로 직접 지정할 수 있다.
|
|
349
|
+
|
|
350
|
+
### `velog_render_cover`
|
|
351
|
+
|
|
352
|
+
글 목록·SNS 미리보기용 1200×630 카드. 제목이 길면 줄바꿈하고, 3줄에도 안 들어가면
|
|
353
|
+
글자 크기를 62 → 40px 까지 줄인다. 줄바꿈도 실측이다 — 공백이 없는 한글 제목은
|
|
354
|
+
글자 단위로 끊는다.
|
|
355
|
+
|
|
356
|
+
만든 뒤 `velog_update_post` 의 `thumbnail` 에 주소를 넣으면 표지가 된다.
|
|
357
|
+
|
|
358
|
+
### `velog_upload_image`
|
|
359
|
+
|
|
360
|
+
로컬 이미지를 올린다. **확장자가 아니라 파일 앞부분 시그니처로 판정한다.**
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
받는 것 PNG · JPEG · GIF · WebP — **청크 구조가 맞는 것**
|
|
364
|
+
안 받는 것 그 외 전부 (SVG 포함) · 10MB 초과 · 디렉터리 · FIFO
|
|
365
|
+
머리만 베껴 붙인 파일 · 잘린 파일 · IEND 뒤에 데이터를 덧붙인 PNG
|
|
366
|
+
0×0 PNG · 빈 IDAT · 화소 청크 없는 WebP · 청크 경계가 깨진 파일
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
머리 8바이트만 보면 "PNG 시그니처 + 아무 텍스트"가 통과한다. 끝맺음을 봐도
|
|
370
|
+
`IHDR payload 안에 IDAT 이라는 글자`를 넣으면 통과한다 — 청크가 아니라 글자를 본
|
|
371
|
+
것이기 때문이다. 그래서 **청크 구조를 실제로 걸어간다**(길이 필드를 따라가며 경계를
|
|
372
|
+
확인). 자세한 규칙은 [security.md](security.md#③-아무-파일이나-인터넷에-올라가지-않는가).
|
|
373
|
+
|
|
374
|
+
완전한 디코딩은 아니다 — 화소가 진짜인지는 안 본다. 대신 **정상 파일을 거부하지 않는지**를
|
|
375
|
+
실물로 확인한다: `sips`·`cwebp` 산출물 7종(IDAT 청크 103개짜리 PNG 포함), 확장
|
|
376
|
+
WebP(VP8X+ALPH+VP8), APNG, 메타데이터 선행 청크, 홀수 길이 패딩 전부 통과.
|
|
377
|
+
|
|
378
|
+
SVG 를 받지 않는 건 텍스트라 시그니처로 가릴 수 없고, 스크립트를 품은 채
|
|
379
|
+
velcdn 도메인에서 서빙되면 그 자체가 문제가 되기 때문이다.
|
|
380
|
+
|
|
381
|
+
`post_id` 를 주면 벨로그가 **그 글이 내 글인지 확인한다** — 아니면 403.
|
|
382
|
+
벨로그에 몇 안 되는 실제 소유권 검사라 쓸 수 있으면 쓴다.
|
|
383
|
+
|
|
384
|
+
### 크롬 의존과 자원 비용
|
|
385
|
+
|
|
386
|
+
렌더 도구 2종만 브라우저를 쓴다. 없으면 그 도구만 안내와 함께 실패하고 나머지 19개는
|
|
387
|
+
그대로 동작한다. 찾는 순서는 `VELOG_CHROME_PATH` → OS 별 기본 설치 경로.
|
|
388
|
+
|
|
389
|
+
**이건 남의 기계에서 도는 물건이라 비용을 밝혀 둔다** (macOS 실측):
|
|
390
|
+
|
|
391
|
+
| | 값 |
|
|
392
|
+
| --- | --- |
|
|
393
|
+
| 그림 한 장 | 크롬 프로세스 9~11개 · 최대 **약 1GB** · 3~4초 |
|
|
394
|
+
| 10회 연속 | 최대치 평평(950~1030MB) · 매회 후 잔존 **0MB / 0개** |
|
|
395
|
+
| MCP 서버 자신 | 54MB → 61MB 에서 안정 |
|
|
396
|
+
|
|
397
|
+
약 1GB 는 크롬 헤드리스의 바닥이고 그림 내용과 거의 무관하다. 줄여 보려고 재봤지만
|
|
398
|
+
`--no-zygote`·`--disable-software-rasterizer` 는 효과가 없었고, `--single-process` 는
|
|
399
|
+
675MB 로 내려가는 대신 **스크린샷이 만들어지지 않아** 쓸 수 없었다.
|
|
400
|
+
|
|
401
|
+
★ **렌더는 한 번에 하나만 돈다.** MCP 클라이언트는 도구를 병렬로 부르므로 이걸 막지
|
|
402
|
+
않으면 그림 다섯 장 요청에 크롬 45개·6GB 가 된다. 실측으로 동시 2회가 17개·1.9GB 였다.
|
|
403
|
+
줄을 세우면 동시 4회를 요청해도 9개·1GB 로 고정된다(전부 성공, 13초).
|
|
404
|
+
한 장에 4초라 줄 세워도 체감 손해가 거의 없다.
|