@clack-platform/cli 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +5 -2
  2. package/dist/cli.js +477 -200
  3. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -7,16 +7,18 @@ import { z as z2 } from "zod";
7
7
 
8
8
  // src/core/errors.ts
9
9
  var CliError = class extends Error {
10
- constructor(message, code = "VALIDATION_ERROR", status = 400, retryAfter) {
10
+ constructor(message, code = "VALIDATION_ERROR", status = 400, retryAfter, recovery) {
11
11
  super(message);
12
12
  this.code = code;
13
13
  this.status = status;
14
14
  this.retryAfter = retryAfter;
15
+ this.recovery = recovery;
15
16
  this.name = "CliError";
16
17
  }
17
18
  code;
18
19
  status;
19
20
  retryAfter;
21
+ recovery;
20
22
  get exitCode() {
21
23
  if (this.code === "USER_API_UNAVAILABLE") return 8;
22
24
  if (this.status === 401) return 3;
@@ -28,6 +30,31 @@ var CliError = class extends Error {
28
30
  return 1;
29
31
  }
30
32
  };
33
+ function describeDetails(details) {
34
+ if (!details || typeof details !== "object" || Array.isArray(details)) return [];
35
+ const d = details;
36
+ const text = (v) => typeof v === "string" && v ? v.replace(/[\p{C}]/gu, "").slice(0, 160) : typeof v === "number" || typeof v === "boolean" ? String(v) : void 0;
37
+ const lines = [];
38
+ const reason = text(d.reason);
39
+ if (reason) lines.push(`\uC0AC\uC720: ${reason}`);
40
+ const file = text(d.file);
41
+ if (file) lines.push(`\uD30C\uC77C: ${file}`);
42
+ if (Array.isArray(d.issues)) {
43
+ for (const issue of d.issues.slice(0, 5)) {
44
+ if (!issue || typeof issue !== "object") continue;
45
+ const i = issue;
46
+ const parts = [["\uD30C\uC77C", i.file], ["\uC704\uCE58", i.pointer], ["\uADDC\uCE59", i.rule], ["\uAC12", i.param]].flatMap(([label, v]) => {
47
+ const t = text(v);
48
+ return t ? [`${label}=${t}`] : [];
49
+ });
50
+ if (parts.length) lines.push(`\uBB38\uC81C: ${parts.join(" ")}`);
51
+ }
52
+ }
53
+ const expected = text(d.expected);
54
+ const actual = text(d.actual);
55
+ if (expected !== void 0 || actual !== void 0) lines.push(`\uAE30\uB300: ${expected ?? "-"} / \uC2E4\uC81C: ${actual ?? "-"}`);
56
+ return lines;
57
+ }
31
58
  function apiError(status, body, retryAfter) {
32
59
  const value2 = body && typeof body === "object" ? body : {};
33
60
  const code = typeof value2.code === "string" ? value2.code : `HTTP_${status}`;
@@ -47,6 +74,9 @@ function apiError(status, body, retryAfter) {
47
74
  if (code === "CONTENT_METADATA_EXTRAS_DISABLED") message += " \uC774 \uD658\uACBD\uC5D0\uC11C\uB294 \uC5B8\uC5B4\uBCC4 \uBA54\uD0C0\uB370\uC774\uD130\xB7\uD0DC\uADF8\uB97C \uC544\uC9C1 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.";
48
75
  if (status === 423 || code === "USER_BANNED") message += " \uACC4\uC815 \uC81C\uD55C \uC0C1\uD0DC\uB97C \uC571\uC5D0\uC11C \uD655\uC778\uD558\uC138\uC694.";
49
76
  if (status === 503 && code.endsWith("DISABLED")) message += " \uD604\uC7AC \uC678\uBD80 \uB3C4\uAD6C \uC811\uADFC\uC774 \uC77C\uC2DC \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
77
+ const located = describeDetails(value2.details);
78
+ if (located.length) message += `
79
+ ${located.map((line) => ` ${line}`).join("\n")}`;
50
80
  const raw = Number(value2.retry_after ?? retryAfter);
51
81
  const retry = Number.isFinite(raw) && raw > 0 ? Math.ceil(raw) : void 0;
52
82
  return new CliError(message, code, status, retry);
@@ -156,7 +186,7 @@ function redact(value2) {
156
186
  // package.json
157
187
  var package_default = {
158
188
  name: "@clack-platform/cli",
159
- version: "0.3.1",
189
+ version: "0.3.3",
160
190
  description: "\uD074\uB799 \uACC4\uC815\uC758 \uC0C1\uD488\xB7\uCF58\uD150\uCE20\xB7\uD504\uB85C\uD544\uC744 \uAD00\uB9AC\uD558\uB294 CLI",
161
191
  type: "module",
162
192
  bin: {
@@ -195,7 +225,7 @@ var package_default = {
195
225
  test: "node scripts/test.mjs",
196
226
  build: "tsup src/cli.ts --format esm --target node20 --clean",
197
227
  check: "pnpm typecheck && node scripts/check-schema-sync.mjs && pnpm test && pnpm build",
198
- "check:release": "pnpm typecheck && node scripts/check-schema-sync.mjs --strict && pnpm test && pnpm build",
228
+ "check:release": "pnpm typecheck && node scripts/check-schema-sync.mjs --strict && node scripts/sync-vendor-types.mjs --check && pnpm test && pnpm build",
199
229
  prepack: "pnpm build"
200
230
  },
201
231
  dependencies: {
@@ -607,8 +637,8 @@ async function authoringRequest(client, raw, surface = "cli") {
607
637
  if (input.action === "tool") return client.request("GET", `${path}/tools/${input.call_id}`);
608
638
  if (input.action === "fill-status") return client.request("GET", `${path}/fill/${input.job_id}`);
609
639
  if (input.action === "upload") {
610
- const stat3 = await lstat2(input.file);
611
- if (!stat3.isFile() || stat3.isSymbolicLink() || stat3.size > 4 * 1024 * 1024 || !stat3.size) throw new CliError("\uC774\uBBF8\uC9C0\uB294 \uC77C\uBC18 \uD30C\uC77C\uC774\uBA70 4 MiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
640
+ const stat4 = await lstat2(input.file);
641
+ if (!stat4.isFile() || stat4.isSymbolicLink() || stat4.size > 4 * 1024 * 1024 || !stat4.size) throw new CliError("\uC774\uBBF8\uC9C0\uB294 \uC77C\uBC18 \uD30C\uC77C\uC774\uBA70 4 MiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
612
642
  const bytes2 = await readFile2(input.file);
613
643
  const body2 = new FormData();
614
644
  body2.append("field", input.field);
@@ -698,8 +728,8 @@ var ConfigStore = class {
698
728
  async read(name2) {
699
729
  const path = join(this.directory, name2);
700
730
  try {
701
- const stat3 = await lstat3(path);
702
- 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");
731
+ const stat4 = await lstat3(path);
732
+ if (!stat4.isFile() || stat4.isSymbolicLink()) throw new CliError("\uC124\uC815 \uD30C\uC77C\uC740 \uC77C\uBC18 \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.", "CONFIG_UNSAFE");
703
733
  if ((name2 === "credentials.json" || name2 === "device-request.json") && process.platform !== "win32") await chmod(path, 384);
704
734
  const body = JSON.parse(await readFile3(path, "utf8"));
705
735
  if (!body || typeof body !== "object" || !("profiles" in body) || !body.profiles || typeof body.profiles !== "object" || Array.isArray(body.profiles)) throw new Error("\uD615\uC2DD");
@@ -2017,22 +2047,87 @@ import { Option as Option2 } from "commander";
2017
2047
 
2018
2048
  // src/lib/platform-mcp.ts
2019
2049
  import { createInterface } from "readline";
2020
- import { z as z10 } from "zod";
2050
+ import { z as z11 } from "zod";
2021
2051
 
2022
2052
  // src/commands/skill.ts
2023
2053
  import { spawn } from "child_process";
2024
2054
  import { existsSync } from "fs";
2025
2055
  import { writeFile as writeFile2 } from "fs/promises";
2026
2056
  import { createRequire } from "module";
2027
- import { join as join3, resolve as resolve8 } from "path";
2028
- import { z as z9 } from "zod";
2057
+ import { join as join3, resolve as resolve9 } from "path";
2058
+ import { z as z10 } from "zod";
2059
+
2060
+ // src/lib/content-thumbnail.ts
2061
+ import { readFile as readFile8, stat as stat2 } from "fs/promises";
2062
+ import { basename as basename3, resolve as resolve6 } from "path";
2063
+ import { imageSize as imageSize2 } from "image-size";
2064
+ import { z as z8 } from "zod";
2065
+ var policySchema = z8.object({
2066
+ thumbnail_upload_enabled: z8.boolean(),
2067
+ thumbnail_upload: z8.object({
2068
+ max_upload_bytes: z8.number().int().positive(),
2069
+ max_input_pixels: z8.number().int().positive(),
2070
+ formats: z8.array(z8.enum(["image/png", "image/jpeg", "image/webp"])).min(1)
2071
+ })
2072
+ });
2073
+ var thumbnailSchema = z8.object({ id: z8.uuid(), status: z8.literal("succeeded"), image_url: z8.url() });
2074
+ async function prepareContentThumbnail(ctx, source) {
2075
+ const response = await ctx.api.request("GET", "/v4/creator/config", { auth: false });
2076
+ const result = policySchema.safeParse(response.data);
2077
+ if (!result.success) throw new CliError("\uC11C\uBC84 \uC378\uB124\uC77C \uC5C5\uB85C\uB4DC \uC124\uC815\uC774 \uD544\uC694\uD569\uB2C8\uB2E4. \uC11C\uBC84 \uC5C5\uB370\uC774\uD2B8 \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.", "THUMBNAIL_CONFIG_REQUIRED", 409);
2078
+ if (!result.data.thumbnail_upload_enabled) throw new CliError("\uC774 \uD658\uACBD\uC5D0\uC11C\uB294 \uC378\uB124\uC77C \uC5C5\uB85C\uB4DC\uB97C \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. content config\uB85C \uAE30\uB2A5 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uC138\uC694.", "THUMBNAIL_UPLOAD_DISABLED", 503);
2079
+ const policy = result.data.thumbnail_upload;
2080
+ const path = resolve6(source);
2081
+ const info = await stat2(path).catch(() => {
2082
+ throw new CliError("\uC378\uB124\uC77C \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", "THUMBNAIL_FILE_NOT_FOUND", 400);
2083
+ });
2084
+ if (!info.isFile() || !info.size) throw new CliError("\uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC740 \uB85C\uCEEC \uC774\uBBF8\uC9C0 \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.", "THUMBNAIL_EMPTY_FILE", 400);
2085
+ if (info.size > policy.max_upload_bytes) throw new CliError(`\uC378\uB124\uC77C\uC740 ${policy.max_upload_bytes}\uBC14\uC774\uD2B8 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.`, "THUMBNAIL_TOO_LARGE", 413);
2086
+ const bytes2 = await readFile8(path).catch(() => {
2087
+ throw new CliError("\uC378\uB124\uC77C \uD30C\uC77C\uC744 \uC77D\uC744 \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", "THUMBNAIL_FILE_UNREADABLE", 400);
2088
+ });
2089
+ if (!bytes2.length || bytes2.length > policy.max_upload_bytes) throw new CliError("\uC77D\uB294 \uB3D9\uC548 \uC378\uB124\uC77C \uD06C\uAE30\uAC00 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4. \uD30C\uC77C\uC744 \uB2E4\uC2DC \uD655\uC778\uD558\uC138\uC694.", "THUMBNAIL_TOO_LARGE", 413);
2090
+ let mime;
2091
+ try {
2092
+ const image2 = imageSize2(bytes2);
2093
+ mime = { png: "image/png", jpg: "image/jpeg", webp: "image/webp" }[image2.type ?? ""] ?? "";
2094
+ if (!policy.formats.includes(mime) || !image2.width || !image2.height || image2.width * image2.height > policy.max_input_pixels) throw new Error();
2095
+ } catch {
2096
+ throw new CliError("\uC11C\uBC84\uAC00 \uC9C0\uC6D0\uD558\uB294 PNG\xB7JPEG\xB7WebP \uC774\uBBF8\uC9C0\uC640 \uC720\uD6A8\uD55C \uD06C\uAE30\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC190\uC0C1\uB41C \uD30C\uC77C\uC740 \uB2E4\uC2DC \uC800\uC7A5\uD558\uC138\uC694.", "THUMBNAIL_INVALID_IMAGE", 422);
2097
+ }
2098
+ return { path, bytes: bytes2, mime };
2099
+ }
2100
+ async function uploadContentThumbnail(ctx, image2) {
2101
+ const body = new FormData();
2102
+ body.append("file", new Blob([new Uint8Array(image2.bytes)], { type: image2.mime }), basename3(image2.path));
2103
+ const response = await ctx.api.request("POST", "/v4/creator/thumbnails", { body });
2104
+ const parsed = thumbnailSchema.safeParse(response.data);
2105
+ if (!parsed.success) throw new CliError("\uC644\uB8CC\uB41C \uC378\uB124\uC77C \uC791\uC5C5 ID\uB97C \uC751\uB2F5\uC5D0\uC11C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC790\uB3D9 \uC7AC\uC5C5\uB85C\uB4DC\uD558\uC9C0 \uB9C8\uC138\uC694.", "INVALID_RESPONSE", 502);
2106
+ return { data: parsed.data, time_contract: response.time_contract };
2107
+ }
2108
+ function thumbnailLinkFailure(ctx, cause, thumbnailId, contentId, creation) {
2109
+ const failure = asCliError(cause);
2110
+ const quote = (value2) => `'${value2.replaceAll("'", "'\\''")}'`;
2111
+ const prefix = `clack --base-url ${quote(ctx.api.settings.baseUrl)}${ctx.options.profile ? ` --profile ${quote(ctx.options.profile)}` : ""}`;
2112
+ const createArgs = ["title", "description", "kind", "policy_version"].flatMap((key) => creation?.[key] === void 0 ? [] : [`--${key.replaceAll("_", "-")} ${quote(String(creation[key]))}`]).join(" ");
2113
+ const command = contentId ? `${prefix} content update ${contentId} --thumbnail-id ${thumbnailId}` : `${prefix} content create ${createArgs} --thumbnail-id ${thumbnailId}`;
2114
+ const verify = contentId ? `${prefix} content status ${contentId}` : `${prefix} content list`;
2115
+ return new CliError(
2116
+ `${failure.message}
2117
+ \uC378\uB124\uC77C \uC5C5\uB85C\uB4DC\uB294 \uC644\uB8CC\uB410\uC2B5\uB2C8\uB2E4 (ID: ${thumbnailId}). ${verify}\uB85C \uC800\uC7A5 \uC5EC\uBD80\uB97C \uD655\uC778\uD55C \uB4A4 ${command}\uB85C \uC5F0\uACB0\uD558\uC138\uC694.`,
2118
+ failure.code,
2119
+ failure.status,
2120
+ failure.retryAfter,
2121
+ { thumbnail_id: thumbnailId, ...contentId ? { content_id: contentId } : {}, verification_command: verify, retry_command: command }
2122
+ );
2123
+ }
2029
2124
 
2030
2125
  // src/commands/content.ts
2031
2126
  import { createHash as createHash2 } from "crypto";
2032
- import { readFile as readFile8, stat as stat2 } from "fs/promises";
2033
- import { basename as basename3, extname, resolve as resolve6 } from "path";
2127
+ import { readFile as readFile9, stat as stat3 } from "fs/promises";
2128
+ import { basename as basename4, extname, resolve as resolve7 } from "path";
2034
2129
  import { Option } from "commander";
2035
- import { z as z8 } from "zod";
2130
+ import { z as z9 } from "zod";
2036
2131
 
2037
2132
  // src/lib/content-package.ts
2038
2133
  import { unzipSync } from "fflate";
@@ -2164,28 +2259,29 @@ function inspectContentPackage(bytes2) {
2164
2259
 
2165
2260
  // src/commands/content.ts
2166
2261
  var MAX_BYTES = 30 * 1024 * 1024;
2167
- var uuid = (value2) => parse(z8.uuid(), value2);
2168
- var createSchema = z8.object({
2169
- title: z8.string().trim().min(1).max(120),
2170
- description: z8.string().trim().max(2e3).default(""),
2171
- kind: z8.enum(["html", "gallery", "slideshow", "video"]).default("html"),
2172
- policy_version: z8.literal("2026-09-22")
2262
+ var uuid = (value2) => parse(z9.uuid(), value2);
2263
+ var createSchema = z9.object({
2264
+ title: z9.string().trim().min(1).max(120),
2265
+ description: z9.string().trim().max(2e3).default(""),
2266
+ kind: z9.enum(["html", "gallery", "slideshow", "video"]).default("html"),
2267
+ policy_version: z9.literal("2026-09-22"),
2268
+ thumbnail_id: z9.uuid().nullable().optional()
2173
2269
  }).strict();
2174
2270
  var METADATA_LANGS = ["ko", "en"];
2175
2271
  var TAG_PATTERN = /^[\p{L}\p{M}\p{N}_]{1,30}$/u;
2176
- var localizedEntry = z8.object({
2177
- title: z8.string().trim().max(120),
2178
- description: z8.string().trim().max(2e3).optional()
2272
+ var localizedEntry = z9.object({
2273
+ title: z9.string().trim().max(120),
2274
+ description: z9.string().trim().max(2e3).optional()
2179
2275
  }).strict();
2180
- var updateSchema = z8.object({
2181
- title: z8.string().trim().max(120).optional(),
2182
- description: z8.string().trim().max(2e3).optional(),
2183
- kind: z8.enum(["html", "gallery", "slideshow", "video"]).optional(),
2184
- thumbnail_id: z8.uuid().nullable().optional(),
2185
- metadata_mode: z8.enum(["single", "localized"]).optional(),
2186
- metadata_lang: z8.enum(METADATA_LANGS).nullable().optional(),
2187
- localized_metadata: z8.partialRecord(z8.enum(METADATA_LANGS), localizedEntry).nullable().optional(),
2188
- tags: z8.array(z8.string().max(64)).max(50).optional()
2276
+ var updateSchema = z9.object({
2277
+ title: z9.string().trim().max(120).optional(),
2278
+ description: z9.string().trim().max(2e3).optional(),
2279
+ kind: z9.enum(["html", "gallery", "slideshow", "video"]).optional(),
2280
+ thumbnail_id: z9.uuid().nullable().optional(),
2281
+ metadata_mode: z9.enum(["single", "localized"]).optional(),
2282
+ metadata_lang: z9.enum(METADATA_LANGS).nullable().optional(),
2283
+ localized_metadata: z9.partialRecord(z9.enum(METADATA_LANGS), localizedEntry).nullable().optional(),
2284
+ tags: z9.array(z9.string().max(64)).max(50).optional()
2189
2285
  }).strict().superRefine((value2, ctx) => {
2190
2286
  const issue = (message) => ctx.addIssue({ code: "custom", message });
2191
2287
  if (!Object.values(value2).some((entry) => entry !== void 0)) issue("\uBCC0\uACBD\uD560 \uD544\uB4DC\uB97C \uD558\uB098 \uC774\uC0C1 \uC9C0\uC815\uD558\uC138\uC694.");
@@ -2221,14 +2317,14 @@ function jsonOption(name2, value2) {
2221
2317
  }
2222
2318
  }
2223
2319
  async function prepareContentFile(source) {
2224
- const file = resolve6(source);
2320
+ const file = resolve7(source);
2225
2321
  const extension = extname(file).toLowerCase();
2226
2322
  if (![".html", ".htm", ".zip"].includes(extension)) throw new CliError("HTML \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.");
2227
2323
  let bytes2;
2228
2324
  try {
2229
- const info = await stat2(file);
2325
+ const info = await stat3(file);
2230
2326
  if (!info.isFile() || info.size === 0 || info.size > MAX_BYTES) throw new Error();
2231
- bytes2 = await readFile8(file);
2327
+ bytes2 = await readFile9(file);
2232
2328
  if (!bytes2.length || bytes2.length > MAX_BYTES) throw new Error();
2233
2329
  } catch {
2234
2330
  throw new CliError("30 MiB \uC774\uD558\uC758 \uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC740 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
@@ -2237,7 +2333,7 @@ async function prepareContentFile(source) {
2237
2333
  bytes: bytes2,
2238
2334
  warnings: extension === ".zip" ? inspectContentPackage(bytes2) : [],
2239
2335
  metadata: {
2240
- filename: basename3(file),
2336
+ filename: basename4(file),
2241
2337
  content_type: extension === ".zip" ? "application/zip" : "text/html",
2242
2338
  byte_size: bytes2.length,
2243
2339
  sha256: createHash2("sha256").update(bytes2).digest("hex")
@@ -2272,8 +2368,25 @@ async function putContentFile(session2, bytes2, fetcher = fetch) {
2272
2368
  throw new CliError("\uD30C\uC77C \uC5C5\uB85C\uB4DC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uC0C8 \uC5C5\uB85C\uB4DC\uB97C \uC2DC\uC791\uD558\uC138\uC694.", "CONTENT_UPLOAD_FAILED", 0);
2273
2369
  }
2274
2370
  }
2371
+ async function saveWithThumbnail(ctx, method, path, body, file, contentId) {
2372
+ if (!file) {
2373
+ await mutate(ctx, method, path, body);
2374
+ return;
2375
+ }
2376
+ const image2 = await prepareContentThumbnail(ctx, file);
2377
+ if (ctx.options.dryRun) {
2378
+ ctx.output({ dry_run: true, method, path, body: { ...body, thumbnail_id: "<\uC5C5\uB85C\uB4DC \uD6C4 \uBC1C\uAE09>" }, thumbnail: { file: image2.path, byte_size: image2.bytes.length } });
2379
+ return;
2380
+ }
2381
+ const uploaded = await uploadContentThumbnail(ctx, image2);
2382
+ try {
2383
+ ctx.output(await ctx.api.request(method, path, { body: { ...body, thumbnail_id: uploaded.data.id } }));
2384
+ } catch (failure) {
2385
+ throw thumbnailLinkFailure(ctx, failure, uploaded.data.id, contentId, method === "POST" ? body : void 0);
2386
+ }
2387
+ }
2275
2388
  var SERVER_KEY_CENTER_ONLY = "\uC11C\uBC84 \uD0A4 \uBC1C\uAE09\xB7\uD68C\uC804\uC740 \uD06C\uB9AC\uC5D0\uC774\uD130 \uC13C\uD130(\uB0B4 \uCF58\uD150\uCE20 \u2192 \uC11C\uBC84 \uD0A4)\uC5D0\uC11C\uB9CC \uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4. CLI\xB7MCP\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC73C\uB85C\uB294 \uC11C\uBC84 \uD0A4 \uC870\uD68C\xB7\uD3D0\uAE30\uB9CC \uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
2276
- var collection = z8.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "\uC774\uB984\uC740 \uC601\uC22B\uC790\xB7\uBC11\uC904\xB7\uBD99\uC784\uD45C 64\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
2389
+ var collection = z9.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "\uC774\uB984\uC740 \uC601\uC22B\uC790\xB7\uBC11\uC904\xB7\uBD99\uC784\uD45C 64\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
2277
2390
  var documentKey = collection;
2278
2391
  var keyId = (value2) => {
2279
2392
  const id2 = String(value2);
@@ -2285,22 +2398,41 @@ function registerContentCommands(program2, runtime2) {
2285
2398
  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 })));
2286
2399
  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")));
2287
2400
  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)}`)));
2288
- 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) => {
2289
- const body = parse(createSchema, { title: opts.title, description: opts.description, kind: opts.kind, policy_version: opts.policyVersion });
2290
- await mutate(ctx, "POST", "/v4/creator/contents", body);
2401
+ 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").addOption(new Option("--thumbnail-file <file>", "\uB85C\uCEEC \uC378\uB124\uC77C \uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB4DC \uD6C4 \uC5F0\uACB0").conflicts("thumbnailId")).addOption(new Option("--thumbnail-id <uuid|none>", "\uBC1C\uAE09\uBC1B\uC740 \uC378\uB124\uC77C ID \uB610\uB294 \uAE30\uBCF8 \uC774\uBBF8\uC9C0").conflicts("thumbnailFile")), async (ctx, _args, opts) => {
2402
+ const body = parse(createSchema, { title: opts.title, description: opts.description, kind: opts.kind, policy_version: opts.policyVersion, thumbnail_id: opts.thumbnailId === void 0 ? void 0 : opts.thumbnailId === "none" ? null : opts.thumbnailId });
2403
+ await saveWithThumbnail(ctx, "POST", "/v4/creator/contents", body, opts.thumbnailFile);
2291
2404
  });
2292
- runtime2.action(content.command("update <id>").description("\uCF58\uD150\uCE20 \uC815\uBCF4(\uC81C\uBAA9\xB7\uC124\uBA85\xB7\uC885\uB958\xB7\uC378\uB124\uC77C\xB7\uBA54\uD0C0\uB370\uC774\uD130\xB7\uD0DC\uADF8) \uC218\uC815 (creator-content:write)").option("--title <text>", "\uC81C\uBAA9 (120\uC790 \uC774\uD558, \uBE48 \uAC12\uC774\uBA74 \uC81C\uBAA9 \uC5C6\uC74C)").option("--description <text>", "\uC124\uBA85 (2000\uC790 \uC774\uD558)").addOption(new Option("--kind <kind>", "\uCF58\uD150\uCE20 \uC885\uB958").choices(["html", "gallery", "slideshow", "video"])).option("--thumbnail-id <uuid|none>", "\uC378\uB124\uC77C \uC791\uC5C5 ID, none\uC774\uBA74 \uAE30\uBCF8 \uC774\uBBF8\uC9C0\uB85C \uB418\uB3CC\uB9BC").addOption(new Option("--metadata-mode <mode>", "\uBA54\uD0C0\uB370\uC774\uD130 \uC785\uB825 \uBC29\uC2DD").choices(["single", "localized"])).option("--metadata-lang <ko|en|none>", "\uB300\uD45C \uC5B8\uC5B4").option("--localized-metadata <json|none>", `\uB300\uD45C \uC5B8\uC5B4 \uC678 \uC5B8\uC5B4\uBCC4 \uC81C\uBAA9\xB7\uC124\uBA85 JSON, \uC608: '{"en":{"title":"Title","description":"About"}}'`).option("--tags <list>", "\uD574\uC2DC\uD0DC\uADF8\uB97C \uC27C\uD45C\uB85C \uAD6C\uBD84\uD574 \uD1B5\uC9F8 \uAD50\uCCB4, \uBE48 \uBB38\uC790\uC5F4\uC774\uBA74 \uBAA8\uB450 \uC0AD\uC81C"), async (ctx, [id2], opts) => {
2405
+ runtime2.action(content.command("update <id>").description("\uCF58\uD150\uCE20 \uC815\uBCF4(\uC81C\uBAA9\xB7\uC124\uBA85\xB7\uC885\uB958\xB7\uC378\uB124\uC77C\xB7\uBA54\uD0C0\uB370\uC774\uD130\xB7\uD0DC\uADF8) \uC218\uC815 (creator-content:write)").option("--title <text>", "\uC81C\uBAA9 (120\uC790 \uC774\uD558, \uBE48 \uAC12\uC774\uBA74 \uC81C\uBAA9 \uC5C6\uC74C)").option("--description <text>", "\uC124\uBA85 (2000\uC790 \uC774\uD558)").addOption(new Option("--kind <kind>", "\uCF58\uD150\uCE20 \uC885\uB958").choices(["html", "gallery", "slideshow", "video"])).addOption(new Option("--thumbnail-id <uuid|none>", "\uC378\uB124\uC77C \uC791\uC5C5 ID, none\uC774\uBA74 \uAE30\uBCF8 \uC774\uBBF8\uC9C0\uB85C \uB418\uB3CC\uB9BC").conflicts("thumbnailFile")).addOption(new Option("--thumbnail-file <file>", "\uB85C\uCEEC \uC378\uB124\uC77C \uC774\uBBF8\uC9C0 \uC5C5\uB85C\uB4DC \uD6C4 \uC5F0\uACB0").conflicts("thumbnailId")).addOption(new Option("--metadata-mode <mode>", "\uBA54\uD0C0\uB370\uC774\uD130 \uC785\uB825 \uBC29\uC2DD").choices(["single", "localized"])).option("--metadata-lang <ko|en|none>", "\uB300\uD45C \uC5B8\uC5B4").option("--localized-metadata <json|none>", `\uB300\uD45C \uC5B8\uC5B4 \uC678 \uC5B8\uC5B4\uBCC4 \uC81C\uBAA9\xB7\uC124\uBA85 JSON, \uC608: '{"en":{"title":"Title","description":"About"}}'`).option("--tags <list>", "\uD574\uC2DC\uD0DC\uADF8\uB97C \uC27C\uD45C\uB85C \uAD6C\uBD84\uD574 \uD1B5\uC9F8 \uAD50\uCCB4, \uBE48 \uBB38\uC790\uC5F4\uC774\uBA74 \uBAA8\uB450 \uC0AD\uC81C"), async (ctx, [id2], opts) => {
2293
2406
  const body = parse(updateSchema, {
2294
2407
  title: opts.title,
2295
2408
  description: opts.description,
2296
2409
  kind: opts.kind,
2297
- thumbnail_id: opts.thumbnailId === void 0 ? void 0 : opts.thumbnailId === "none" ? null : opts.thumbnailId,
2410
+ thumbnail_id: opts.thumbnailFile ? null : opts.thumbnailId === void 0 ? void 0 : opts.thumbnailId === "none" ? null : opts.thumbnailId,
2298
2411
  metadata_mode: opts.metadataMode,
2299
2412
  metadata_lang: opts.metadataLang === void 0 ? void 0 : opts.metadataLang === "none" ? null : opts.metadataLang,
2300
2413
  localized_metadata: opts.localizedMetadata === void 0 ? void 0 : jsonOption("--localized-metadata", opts.localizedMetadata),
2301
2414
  tags: opts.tags === void 0 ? void 0 : csv(opts.tags)
2302
2415
  });
2303
- await mutate(ctx, "PATCH", `/v4/creator/contents/${uuid(id2)}`, body);
2416
+ await saveWithThumbnail(ctx, "PATCH", `/v4/creator/contents/${uuid(id2)}`, body, opts.thumbnailFile, uuid(id2));
2417
+ });
2418
+ runtime2.action(content.command("thumbnail <file>").description("\uB85C\uCEEC \uC378\uB124\uC77C \uC5C5\uB85C\uB4DC\uC640 \uC120\uD0DD\uC801 \uCF58\uD150\uCE20 \uC5F0\uACB0 (creator-content:write)").option("--content <uuid>", "\uC5C5\uB85C\uB4DC \uD6C4 \uC774 \uCF58\uD150\uCE20\uC5D0 \uC5F0\uACB0"), async (ctx, [file], opts) => {
2419
+ const contentId = opts.content === void 0 ? void 0 : uuid(opts.content);
2420
+ const image2 = await prepareContentThumbnail(ctx, file);
2421
+ if (ctx.options.dryRun) {
2422
+ ctx.output({ dry_run: true, path: "/v4/creator/thumbnails", file: image2.path, byte_size: image2.bytes.length, ...contentId ? { content_id: contentId } : {} });
2423
+ return;
2424
+ }
2425
+ const uploaded = await uploadContentThumbnail(ctx, image2);
2426
+ if (!contentId) {
2427
+ ctx.output(uploaded);
2428
+ return;
2429
+ }
2430
+ try {
2431
+ const result = await ctx.api.request("PATCH", `/v4/creator/contents/${contentId}`, { body: { thumbnail_id: uploaded.data.id } });
2432
+ ctx.output({ ...result, thumbnail: uploaded.data });
2433
+ } catch (failure) {
2434
+ throw thumbnailLinkFailure(ctx, failure, uploaded.data.id, contentId);
2435
+ }
2304
2436
  });
2305
2437
  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", "floating_close_safe_area"])).addOption(new Option("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"])), async (ctx, [id2, file], opts) => {
2306
2438
  const path = `/v4/creator/contents/${uuid(id2)}`;
@@ -2406,15 +2538,52 @@ function dataRootField(schema, name2) {
2406
2538
  }
2407
2539
  return field2;
2408
2540
  }
2409
- function inspectEditorSemantics(skill, data, fail2) {
2541
+ var SEMANTICS_NOTES = {
2542
+ // 스킬 에디터 선언
2543
+ editor_document_permissions: () => "\uC5D0\uB514\uD130\uC5D0\uB294 document.read\uC640 document.write \uAD8C\uD55C\uC774 \uBAA8\uB450 \uD544\uC694\uD569\uB2C8\uB2E4.",
2544
+ ai_suggest_needs_ai_fill: () => "ai.suggest \uAD8C\uD55C\uC5D0\uB294 authoring.ai_fill.enabled\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2545
+ image_permission_needs_tool: () => "tools.image.generate \uAD8C\uD55C\uC5D0\uB294 authoring.tools\uC758 image.generate \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2546
+ editor_slot_aspect: () => "\uC5D0\uB514\uD130 \uC2A4\uD0AC\uC740 \uC2AC\uB86F \uBE44\uC728(aspect: slot)\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC790\uC0B0 \uB178\uB4DC\uC758 x-clack-asset.aspect\uB97C \uC4F0\uC138\uC694.",
2547
+ asset_limits_missing: () => "\uC790\uC0B0 \uB178\uB4DC\uAC00 \uC788\uB294 \uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8\uC5D0\uB294 output.data.assets \uD55C\uB3C4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2548
+ asset_limits_unused: () => "\uC790\uC0B0 \uB178\uB4DC\uAC00 \uC5C6\uB294\uB370 \uC790\uC0B0 \uD55C\uB3C4\uB97C \uC120\uC5B8\uD588\uC2B5\uB2C8\uB2E4.",
2549
+ image_permission_no_asset: () => "\uC774\uBBF8\uC9C0\uB97C \uB123\uC744 \uC790\uC0B0 \uB178\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
2550
+ ai_suggest_no_node: () => "x-clack-ai.suggest\uAC00 \uCF1C\uC9C4 \uB178\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
2551
+ metadata_text_key: () => "\uACF5\uAC1C \uBB38\uC790\uC5F4 \uB8E8\uD2B8 \uD0A4\uB97C \uAC00\uB9AC\uCF1C\uC57C \uD569\uB2C8\uB2E4.",
2552
+ metadata_asset_key: () => "\uACF5\uAC1C \uC790\uC0B0 \uB8E8\uD2B8 \uD0A4\uB97C \uAC00\uB9AC\uCF1C\uC57C \uD569\uB2C8\uB2E4(\uC778\uB371\uC2A4 \uD45C\uAE30 \uBD88\uAC00).",
2553
+ // 콘텐츠 인트로
2554
+ intro_image_not_slot: () => "\uC778\uD2B8\uB85C \uC774\uBBF8\uC9C0\uB294 output.asset_slots\uC758 \uD544\uB4DC\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
2555
+ intro_images_over: ({ max }) => `\uC778\uD2B8\uB85C \uC774\uBBF8\uC9C0\uB294 \uC2AC\uB86F \uCD5C\uB300 \uC7A5\uC218 \uD569\uACC4\uAC00 ${max}\uC7A5 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.`,
2556
+ intro_text_field: () => "\uACF5\uAC1C \uBB38\uC790\uC5F4(text\xB7textarea) \uD3FC \uD544\uB4DC\uB97C \uAC00\uB9AC\uCF1C\uC57C \uD569\uB2C8\uB2E4.",
2557
+ intro_text_too_long: ({ max }) => `\uAC00\uB9AC\uD0A4\uB294 \uD3FC \uD544\uB4DC\uC758 \uCD5C\uB300 \uAE38\uC774\uAC00 ${max}\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.`,
2558
+ intro_section_field: () => "\uC18C\uAC1C \uC139\uC158\uC740 \uACF5\uAC1C \uBB38\uC790\uC5F4(text\xB7textarea) \uD3FC \uD544\uB4DC\uB9CC \uAC00\uB9AC\uD0AC \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
2559
+ intro_section_too_long: () => "\uC18C\uAC1C \uC139\uC158\uC758 \uCD5C\uB300 \uAE38\uC774\uB294 2,000\uC790\uC785\uB2C8\uB2E4.",
2560
+ intro_sections_total: () => "\uC18C\uAC1C \uC139\uC158\uC758 \uCD5C\uB300 \uAE38\uC774 \uD569\uACC4\uB294 6,000\uC790\uC785\uB2C8\uB2E4.",
2561
+ intro_fallback_field: () => "\uBB38\uC790\uC5F4(text\xB7textarea) \uD3FC \uD544\uB4DC\uB9CC \uC790\uB3D9 \uAD6C\uC131\uC5D0 \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
2562
+ intro_fallback_too_long: ({ max }) => `\uC790\uB3D9 \uAD6C\uC131 \uCD5C\uB300 \uAE38\uC774(\uB77C\uBCA8 \uD3EC\uD568)\uAC00 ${max}\uC790\uB97C \uB118\uC2B5\uB2C8\uB2E4.`,
2563
+ // 도구·이미지 슬롯
2564
+ tool_duplicate: () => "\uB3C4\uAD6C \uC774\uB984\uC744 \uC911\uBCF5 \uC120\uC5B8\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
2565
+ tool_batch_over_calls: () => "\uC77C\uAD04 \uC0DD\uC131 \uC218\uB294 \uC138\uC158 \uD638\uCD9C \uC218\uB97C \uB118\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
2566
+ slot_field_duplicate: () => "\uC2AC\uB86F \uD544\uB4DC\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
2567
+ slot_multiple_range: () => "\uB2E4\uC911 \uC2AC\uB86F \uCD5C\uC18C \uC218\uB294 \uCD5C\uB300 \uC218\uBCF4\uB2E4 \uD074 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
2568
+ slot_path_duplicate: () => "\uC2AC\uB86F \uD30C\uC77C \uACBD\uB85C\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
2569
+ slot_widget_mismatch: () => "\uC2AC\uB86F\uACFC \uC774\uBBF8\uC9C0 \uC704\uC82F \uC885\uB958\uAC00 \uC77C\uCE58\uD574\uC57C \uD569\uB2C8\uB2E4.",
2570
+ // 폼 필드 참조
2571
+ form_field_missing: () => "\uC120\uC5B8\uD55C \uD3FC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
2572
+ slot_index_invalid: () => "\uB2E4\uC911 \uC774\uBBF8\uC9C0 \uC2AC\uB86F\uC758 \uC720\uD6A8\uD55C \uC778\uB371\uC2A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2573
+ content_ref_declaration: () => "content_ref\uC5D0\uB294 \uBB38\uC790\uC5F4\uACFC x-clack-source \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2574
+ content_ref_import_chain: () => "\uB2E4\uB978 content_ref \uD544\uB4DC\uB85C \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
2575
+ source_without_content_ref: () => "x-clack-source\uB294 content_ref\uC5D0\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4.",
2576
+ image_list_declaration: () => "image_list\uC5D0\uB294 \uAC1D\uCCB4 \uBC30\uC5F4\uACFC \uC790\uC0B0 \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."
2577
+ };
2578
+ function inspectEditorSemantics(skill, data, fail) {
2410
2579
  const editor = skill.authoring?.editor;
2411
2580
  if (!editor) return;
2412
2581
  const permissions = new Set(editor.permissions ?? []);
2413
- if (!permissions.has("document.read") || !permissions.has("document.write")) fail2("/authoring/editor/permissions", "\uC5D0\uB514\uD130\uC5D0\uB294 document.read\uC640 document.write \uAD8C\uD55C\uC774 \uBAA8\uB450 \uD544\uC694\uD569\uB2C8\uB2E4.");
2414
- if (permissions.has("ai.suggest") && skill.authoring?.ai_fill?.enabled !== true) fail2("/authoring/editor/permissions", "ai.suggest \uAD8C\uD55C\uC5D0\uB294 authoring.ai_fill.enabled\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
2582
+ if (!permissions.has("document.read") || !permissions.has("document.write")) fail("/authoring/editor/permissions", "editor_document_permissions");
2583
+ if (permissions.has("ai.suggest") && skill.authoring?.ai_fill?.enabled !== true) fail("/authoring/editor/permissions", "ai_suggest_needs_ai_fill");
2415
2584
  const imageTool = (skill.authoring?.tools ?? []).find((tool) => tool.tool === "image.generate");
2416
- if (permissions.has("tools.image.generate") && !imageTool) fail2("/authoring/editor/permissions", "tools.image.generate \uAD8C\uD55C\uC5D0\uB294 authoring.tools\uC758 image.generate \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
2417
- if (imageTool?.aspect === "slot") fail2("/authoring/tools", "\uC5D0\uB514\uD130 \uC2A4\uD0AC\uC740 \uC2AC\uB86F \uBE44\uC728(aspect: slot)\uC744 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC790\uC0B0 \uB178\uB4DC\uC758 x-clack-asset.aspect\uB97C \uC4F0\uC138\uC694.");
2585
+ if (permissions.has("tools.image.generate") && !imageTool) fail("/authoring/editor/permissions", "image_permission_needs_tool");
2586
+ if (imageTool?.aspect === "slot") fail("/authoring/tools", "editor_slot_aspect");
2418
2587
  if (!data) return;
2419
2588
  let assets = 0;
2420
2589
  let suggest = 0;
@@ -2422,20 +2591,20 @@ function inspectEditorSemantics(skill, data, fail2) {
2422
2591
  if (node["x-clack-asset"]) assets++;
2423
2592
  if (node["x-clack-ai"]?.suggest === true) suggest++;
2424
2593
  });
2425
- if (assets > 0 && !skill.output?.data?.assets) fail2("/output/data/assets", "\uC790\uC0B0 \uB178\uB4DC\uAC00 \uC788\uB294 \uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8\uC5D0\uB294 output.data.assets \uD55C\uB3C4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
2426
- if (assets === 0 && skill.output?.data?.assets) fail2("/output/data/assets", "\uC790\uC0B0 \uB178\uB4DC\uAC00 \uC5C6\uB294\uB370 \uC790\uC0B0 \uD55C\uB3C4\uB97C \uC120\uC5B8\uD588\uC2B5\uB2C8\uB2E4.");
2427
- if (assets === 0 && permissions.has("tools.image.generate")) fail2("/authoring/editor/permissions", "\uC774\uBBF8\uC9C0\uB97C \uB123\uC744 \uC790\uC0B0 \uB178\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2428
- if (suggest === 0 && permissions.has("ai.suggest")) fail2("/authoring/editor/permissions", "x-clack-ai.suggest\uAC00 \uCF1C\uC9C4 \uB178\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2594
+ if (assets > 0 && !skill.output?.data?.assets) fail("/output/data/assets", "asset_limits_missing");
2595
+ if (assets === 0 && skill.output?.data?.assets) fail("/output/data/assets", "asset_limits_unused");
2596
+ if (assets === 0 && permissions.has("tools.image.generate")) fail("/authoring/editor/permissions", "image_permission_no_asset");
2597
+ if (suggest === 0 && permissions.has("ai.suggest")) fail("/authoring/editor/permissions", "ai_suggest_no_node");
2429
2598
  const metadata = skill.output?.metadata ?? {};
2430
2599
  for (const key of ["title", "description"]) {
2431
2600
  if (metadata[key] === void 0) continue;
2432
2601
  const field2 = dataRootField(data, metadata[key]);
2433
- if (!field2 || field2.type !== "string" || field2["x-clack-asset"] || field2["x-clack-visibility"] !== "public") fail2(`/output/metadata/${key}`, "\uACF5\uAC1C \uBB38\uC790\uC5F4 \uB8E8\uD2B8 \uD0A4\uB97C \uAC00\uB9AC\uCF1C\uC57C \uD569\uB2C8\uB2E4.");
2602
+ if (!field2 || field2.type !== "string" || field2["x-clack-asset"] || field2["x-clack-visibility"] !== "public") fail(`/output/metadata/${key}`, "metadata_text_key");
2434
2603
  }
2435
2604
  for (const key of ["thumbnail", "thumbnail_fallback"]) {
2436
2605
  if (metadata[key] === void 0) continue;
2437
2606
  const field2 = dataRootField(data, metadata[key]);
2438
- if (!field2 || !field2["x-clack-asset"] || field2["x-clack-visibility"] !== "public") fail2(`/output/metadata/${key}`, "\uACF5\uAC1C \uC790\uC0B0 \uB8E8\uD2B8 \uD0A4\uB97C \uAC00\uB9AC\uCF1C\uC57C \uD569\uB2C8\uB2E4(\uC778\uB371\uC2A4 \uD45C\uAE30 \uBD88\uAC00).");
2607
+ if (!field2 || !field2["x-clack-asset"] || field2["x-clack-visibility"] !== "public") fail(`/output/metadata/${key}`, "metadata_asset_key");
2439
2608
  }
2440
2609
  }
2441
2610
  var INTRO_MAX_IMAGES = 8;
@@ -2452,19 +2621,19 @@ function introFieldLabelMaxLength(name2, field2) {
2452
2621
  }
2453
2622
  return Math.max(...lengths);
2454
2623
  }
2455
- function inspectIntroSemantics(skill, form, slots, fail2) {
2624
+ function inspectIntroSemantics(skill, form, slots, fail) {
2456
2625
  const intro = skill.output?.intro;
2457
2626
  if (!intro) return;
2458
2627
  let images = 0;
2459
2628
  for (const name2 of intro.images ?? []) {
2460
2629
  const slot = slots.find((entry) => entry.field === name2);
2461
2630
  if (!slot) {
2462
- fail2("/output/intro/images", "\uC778\uD2B8\uB85C \uC774\uBBF8\uC9C0\uB294 output.asset_slots\uC758 \uD544\uB4DC\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
2631
+ fail("/output/intro/images", "intro_image_not_slot");
2463
2632
  continue;
2464
2633
  }
2465
2634
  images += slot.multiple ? slot.multiple.max : 1;
2466
2635
  }
2467
- if (images > INTRO_MAX_IMAGES) fail2("/output/intro/images", `\uC778\uD2B8\uB85C \uC774\uBBF8\uC9C0\uB294 \uC2AC\uB86F \uCD5C\uB300 \uC7A5\uC218 \uD569\uACC4\uAC00 ${INTRO_MAX_IMAGES}\uC7A5 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
2636
+ if (images > INTRO_MAX_IMAGES) fail("/output/intro/images", "intro_images_over", { max: INTRO_MAX_IMAGES });
2468
2637
  if (!form) return;
2469
2638
  const properties = form.properties ?? {};
2470
2639
  const textField = (name2) => {
@@ -2474,54 +2643,64 @@ function inspectIntroSemantics(skill, form, slots, fail2) {
2474
2643
  for (const [key, max] of [["creator_comment", INTRO_CREATOR_COMMENT_MAX], ["description", INTRO_DESCRIPTION_MAX]]) {
2475
2644
  if (intro[key] === void 0) continue;
2476
2645
  const field2 = textField(intro[key]);
2477
- if (!field2 || field2["x-clack-visibility"] !== "public") fail2(`/output/intro/${key}`, "\uACF5\uAC1C \uBB38\uC790\uC5F4(text\xB7textarea) \uD3FC \uD544\uB4DC\uB97C \uAC00\uB9AC\uCF1C\uC57C \uD569\uB2C8\uB2E4.");
2478
- else if (field2.maxLength > max) fail2(`/output/intro/${key}`, `\uAC00\uB9AC\uD0A4\uB294 \uD3FC \uD544\uB4DC\uC758 \uCD5C\uB300 \uAE38\uC774\uAC00 ${max}\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
2646
+ if (!field2 || field2["x-clack-visibility"] !== "public") fail(`/output/intro/${key}`, "intro_text_field");
2647
+ else if (field2.maxLength > max) fail(`/output/intro/${key}`, "intro_text_too_long", { max });
2648
+ }
2649
+ let sectionLength = 0;
2650
+ for (const [key, name2] of Object.entries(intro.sections ?? {})) {
2651
+ const field2 = textField(name2);
2652
+ if (!field2 || field2["x-clack-visibility"] !== "public") fail(`/output/intro/sections/${key}`, "intro_section_field");
2653
+ else {
2654
+ if (field2.maxLength > 2e3) fail(`/output/intro/sections/${key}`, "intro_section_too_long");
2655
+ sectionLength += field2.maxLength;
2656
+ }
2479
2657
  }
2658
+ if (sectionLength > 6e3) fail("/output/intro/sections", "intro_sections_total");
2480
2659
  const fallback = intro.description_fallback ?? [];
2481
2660
  let composed = 0;
2482
2661
  for (const name2 of fallback) {
2483
2662
  const field2 = textField(name2);
2484
2663
  if (!field2) {
2485
- fail2("/output/intro/description_fallback", "\uBB38\uC790\uC5F4(text\xB7textarea) \uD3FC \uD544\uB4DC\uB9CC \uC790\uB3D9 \uAD6C\uC131\uC5D0 \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
2664
+ fail("/output/intro/description_fallback", "intro_fallback_field");
2486
2665
  continue;
2487
2666
  }
2488
2667
  composed += introFieldLabelMaxLength(name2, field2) + 1 + field2.maxLength;
2489
2668
  }
2490
2669
  composed += Math.max(0, fallback.length - 1) * 2;
2491
- if (composed > INTRO_DESCRIPTION_MAX) fail2("/output/intro/description_fallback", `\uC790\uB3D9 \uAD6C\uC131 \uCD5C\uB300 \uAE38\uC774(\uB77C\uBCA8 \uD3EC\uD568)\uAC00 ${INTRO_DESCRIPTION_MAX}\uC790\uB97C \uB118\uC2B5\uB2C8\uB2E4.`);
2670
+ if (composed > INTRO_DESCRIPTION_MAX) fail("/output/intro/description_fallback", "intro_fallback_too_long", { max: INTRO_DESCRIPTION_MAX });
2492
2671
  }
2493
2672
  function inspectAuthoringSemantics(skill, form, data) {
2494
2673
  const errors = [];
2495
- const fail2 = (path, message) => errors.push({ path, message });
2496
- inspectEditorSemantics(skill, data, fail2);
2674
+ const fail = (path, code, args) => errors.push({ path, message: SEMANTICS_NOTES[code](args ?? {}), code, ...args ? { args } : {} });
2675
+ inspectEditorSemantics(skill, data, fail);
2497
2676
  const tools = skill.authoring?.tools ?? [];
2498
- if (new Set(tools.map((tool) => tool.tool)).size !== tools.length) fail2("/authoring/tools", "\uB3C4\uAD6C \uC774\uB984\uC744 \uC911\uBCF5 \uC120\uC5B8\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2499
- for (const tool of tools) if (tool.batch_max > tool.max_calls) fail2("/authoring/tools", "\uC77C\uAD04 \uC0DD\uC131 \uC218\uB294 \uC138\uC158 \uD638\uCD9C \uC218\uB97C \uB118\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2677
+ if (new Set(tools.map((tool) => tool.tool)).size !== tools.length) fail("/authoring/tools", "tool_duplicate");
2678
+ for (const tool of tools) if (tool.batch_max > tool.max_calls) fail("/authoring/tools", "tool_batch_over_calls");
2500
2679
  const slots = skill.output?.asset_slots ?? [];
2501
2680
  const paths = /* @__PURE__ */ new Set();
2502
2681
  const fields3 = /* @__PURE__ */ new Set();
2503
2682
  for (const slot of slots) {
2504
- if (fields3.has(slot.field)) fail2("/output/asset_slots", "\uC2AC\uB86F \uD544\uB4DC\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2683
+ if (fields3.has(slot.field)) fail("/output/asset_slots", "slot_field_duplicate");
2505
2684
  fields3.add(slot.field);
2506
- if (slot.multiple && slot.multiple.min > slot.multiple.max) fail2("/output/asset_slots", "\uB2E4\uC911 \uC2AC\uB86F \uCD5C\uC18C \uC218\uB294 \uCD5C\uB300 \uC218\uBCF4\uB2E4 \uD074 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2685
+ if (slot.multiple && slot.multiple.min > slot.multiple.max) fail("/output/asset_slots", "slot_multiple_range");
2507
2686
  for (let index = 0; index < (slot.multiple?.max ?? 1); index++) {
2508
2687
  const path = slot.multiple ? slot.multiple.path.replace("{n}", String(index)) : slot.path;
2509
- if (paths.has(path)) fail2("/output/asset_slots", "\uC2AC\uB86F \uD30C\uC77C \uACBD\uB85C\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2688
+ if (paths.has(path)) fail("/output/asset_slots", "slot_path_duplicate");
2510
2689
  paths.add(path);
2511
2690
  }
2512
2691
  }
2513
- inspectIntroSemantics(skill, form, slots, fail2);
2692
+ inspectIntroSemantics(skill, form, slots, fail);
2514
2693
  if (!form) return errors;
2515
2694
  const properties = form.properties ?? {};
2516
2695
  const exists = (expression, path) => {
2517
2696
  const match = /^([a-z][a-z0-9_-]*)(?:\[(\d+)\])?$/.exec(expression);
2518
2697
  if (!match || !Object.hasOwn(properties, match[1])) {
2519
- fail2(path, "\uC120\uC5B8\uD55C \uD3FC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2698
+ fail(path, "form_field_missing");
2520
2699
  return;
2521
2700
  }
2522
2701
  if (match[2] !== void 0) {
2523
2702
  const slot = slots.find((value2) => value2.field === match[1]);
2524
- if (!slot?.multiple || Number(match[2]) >= slot.multiple.max) fail2(path, "\uB2E4\uC911 \uC774\uBBF8\uC9C0 \uC2AC\uB86F\uC758 \uC720\uD6A8\uD55C \uC778\uB371\uC2A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
2703
+ if (!slot?.multiple || Number(match[2]) >= slot.multiple.max) fail(path, "slot_index_invalid");
2525
2704
  }
2526
2705
  };
2527
2706
  const entity = skill.output?.entity_map;
@@ -2531,17 +2710,17 @@ function inspectAuthoringSemantics(skill, form, data) {
2531
2710
  }
2532
2711
  for (const slot of slots) {
2533
2712
  const node = properties[slot.field];
2534
- if (!node || node["x-clack-widget"] !== (slot.multiple ? "image_list" : "image")) fail2("/output/asset_slots", "\uC2AC\uB86F\uACFC \uC774\uBBF8\uC9C0 \uC704\uC82F \uC885\uB958\uAC00 \uC77C\uCE58\uD574\uC57C \uD569\uB2C8\uB2E4.");
2713
+ if (!node || node["x-clack-widget"] !== (slot.multiple ? "image_list" : "image")) fail("/output/asset_slots", "slot_widget_mismatch");
2535
2714
  }
2536
2715
  for (const [name2, node] of Object.entries(properties)) {
2537
2716
  if (node["x-clack-widget"] === "content_ref") {
2538
- if (node.type !== "string" || !node["x-clack-source"]) fail2(`/properties/${name2}`, "content_ref\uC5D0\uB294 \uBB38\uC790\uC5F4\uACFC x-clack-source \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
2717
+ if (node.type !== "string" || !node["x-clack-source"]) fail(`/properties/${name2}`, "content_ref_declaration");
2539
2718
  for (const target of Object.keys(node["x-clack-source"]?.import ?? {})) {
2540
2719
  exists(target, `/properties/${name2}/x-clack-source/import`);
2541
- if (properties[target]?.["x-clack-widget"] === "content_ref") fail2(`/properties/${name2}`, "\uB2E4\uB978 content_ref \uD544\uB4DC\uB85C \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2720
+ if (properties[target]?.["x-clack-widget"] === "content_ref") fail(`/properties/${name2}`, "content_ref_import_chain");
2542
2721
  }
2543
- } else if (node["x-clack-source"]) fail2(`/properties/${name2}`, "x-clack-source\uB294 content_ref\uC5D0\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4.");
2544
- if (node["x-clack-widget"] === "image_list" && (node.type !== "array" || node.items?.type !== "object" || !node["x-clack-asset"])) fail2(`/properties/${name2}`, "image_list\uC5D0\uB294 \uAC1D\uCCB4 \uBC30\uC5F4\uACFC \uC790\uC0B0 \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
2722
+ } else if (node["x-clack-source"]) fail(`/properties/${name2}`, "source_without_content_ref");
2723
+ if (node["x-clack-widget"] === "image_list" && (node.type !== "array" || node.items?.type !== "object" || !node["x-clack-asset"])) fail(`/properties/${name2}`, "image_list_declaration");
2545
2724
  }
2546
2725
  return errors;
2547
2726
  }
@@ -2550,7 +2729,7 @@ function inspectAuthoringSemantics(skill, form, data) {
2550
2729
  import { createHash as createHash4 } from "crypto";
2551
2730
  import { lstat as lstat4, open as open3, readdir } from "fs/promises";
2552
2731
  import { constants } from "fs";
2553
- import { basename as basename4, extname as extname2, join as join2, resolve as resolve7 } from "path";
2732
+ import { basename as basename5, extname as extname2, join as join2, resolve as resolve8 } from "path";
2554
2733
  import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
2555
2734
  import { unzipSync as unzipSync2, zipSync } from "fflate";
2556
2735
  import { parseDocument } from "yaml";
@@ -2561,7 +2740,6 @@ import { Ajv2020 } from "ajv/dist/2020.js";
2561
2740
 
2562
2741
  // src/vendor/clack-api-v4-contracts/output-data-inspect.mjs
2563
2742
  var bytes = (value2) => Buffer.byteLength(JSON.stringify(value2), "utf8");
2564
- var fail = (path, message) => ({ path, message });
2565
2743
  var OUTPUT_DATA_LIMITS = { schema_bytes: 65536, depth: 6, properties: 400, public_bytes: 262144, private_bytes: 65536, ui_state_bytes: 262144 };
2566
2744
  var SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "integer", "number", "boolean"]);
2567
2745
  var SCALAR_KEYS = {
@@ -2573,84 +2751,115 @@ var SCALAR_KEYS = {
2573
2751
  boolean: []
2574
2752
  };
2575
2753
  var TYPED_KEYS = new Set(Object.values(SCALAR_KEYS).flat());
2754
+ var RANGE_LABELS = { length: "\uAE38\uC774", items: "\uD56D\uBAA9 \uC218", value: "\uAC12" };
2755
+ var OUTPUT_DATA_NOTES = {
2756
+ schema_too_large: () => "\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8\uB294 64 KiB \uC774\uD558\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
2757
+ root_required_undeclared: ({ key }) => `\uC120\uC5B8\uD558\uC9C0 \uC54A\uC740 \uB8E8\uD2B8 \uD0A4 ${key}\uC785\uB2C8\uB2E4.`,
2758
+ public_root_key_missing: () => "\uACF5\uAC1C \uB8E8\uD2B8 \uD0A4\uAC00 \uD558\uB098 \uC774\uC0C1 \uD544\uC694\uD569\uB2C8\uB2E4.",
2759
+ ref_cycle: () => "\uC21C\uD658 \uCC38\uC870\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
2760
+ ref_not_local: () => "\uB85C\uCEEC $defs \uCC38\uC870\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4.",
2761
+ depth_over: () => "\uB370\uC774\uD130 \uBB38\uC11C \uAE4A\uC774\uB294 \uB8E8\uD2B8 \uC544\uB798 6 \uC774\uD558\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
2762
+ ref_extra_keys: () => "$ref\uC5D0\uB294 \uC124\uBA85 \uD0A4\uB9CC \uD568\uAED8 \uB458 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
2763
+ one_of_discriminator_missing: () => "oneOf\uC5D0\uB294 x-clack-discriminator\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2764
+ one_of_typed_keys: () => "oneOf \uB178\uB4DC\uC5D0\uB294 \uD615\uC2DD \uD0A4\uB97C \uD568\uAED8 \uB458 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
2765
+ one_of_branch_shape: () => "\uAC01 oneOf \uBD84\uAE30\uB294 \uD310\uBCC4\uC790 \uBB38\uC790\uC5F4 const\uB97C \uD544\uC218\uB85C \uAC00\uC9C4 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
2766
+ discriminator_duplicate: ({ value: value2 }) => `\uD310\uBCC4\uC790 \uAC12 ${value2}\uC774 \uC911\uBCF5\uB429\uB2C8\uB2E4.`,
2767
+ discriminator_misplaced: () => "x-clack-discriminator\uB294 oneOf\uC5D0\uB9CC \uB461\uB2C8\uB2E4.",
2768
+ type_missing: () => "type\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
2769
+ key_not_for_type: ({ type }) => `${type}\uC5D0 \uC4F8 \uC218 \uC5C6\uB294 \uD0A4\uC785\uB2C8\uB2E4.`,
2770
+ range_invalid: ({ range }) => `${RANGE_LABELS[range]} \uBC94\uC704\uAC00 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`,
2771
+ string_max_length_missing: () => "\uBB38\uC790\uC5F4 maxLength\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2772
+ array_items_missing: () => "\uBC30\uC5F4\uC5D0\uB294 items\uC640 maxItems\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2773
+ unique_items_not_scalar: () => "uniqueItems\uB294 \uC2A4\uCE7C\uB77C \uBC30\uC5F4\uC5D0\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
2774
+ object_shape: () => "\uAC1D\uCCB4\uC5D0\uB294 properties\uC640 additionalProperties: false\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
2775
+ required_undeclared: ({ key }) => `\uC120\uC5B8\uD558\uC9C0 \uC54A\uC740 \uD0A4 ${key}\uC744 required\uC5D0 \uB450\uC5C8\uC2B5\uB2C8\uB2E4.`,
2776
+ enum_type_mismatch: () => "enum \uAC12\uC758 \uD615\uC2DD\uC774 type\uACFC \uB2E4\uB985\uB2C8\uB2E4.",
2777
+ asset_node_shape: () => "\uC790\uC0B0 \uB178\uB4DC\uB294 {type:string, format:uuid, maxLength:36}\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
2778
+ asset_in_private: () => "\uBE44\uACF5\uAC1C \uD0A4\uC5D0\uB294 \uC790\uC0B0\uC744 \uB458 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
2779
+ format_not_string: () => "format\uC740 \uBB38\uC790\uC5F4\uC5D0\uB9CC \uC501\uB2C8\uB2E4.",
2780
+ ai_suggest_private: () => "AI \uC81C\uC548\uC740 \uACF5\uAC1C \uD0A4\uC5D0\uB9CC \uCF24 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
2781
+ properties_over: () => "\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8 \uC18D\uC131\uC740 400\uAC1C \uC774\uD558\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
2782
+ ai_suggest_has_asset: () => "AI \uC81C\uC548 \uD558\uC704 \uD2B8\uB9AC\uC5D0 \uC790\uC0B0 \uB178\uB4DC\uB97C \uB458 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."
2783
+ };
2576
2784
  function inspectOutputData(schema) {
2577
2785
  const errors = [];
2578
- if (bytes(schema) > OUTPUT_DATA_LIMITS.schema_bytes) errors.push(fail("/", "\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8\uB294 64 KiB \uC774\uD558\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4."));
2579
- for (const name2 of schema.required ?? []) if (!Object.hasOwn(schema.properties, name2)) errors.push(fail("/required", `\uC120\uC5B8\uD558\uC9C0 \uC54A\uC740 \uB8E8\uD2B8 \uD0A4 ${name2}\uC785\uB2C8\uB2E4.`));
2580
- if (!Object.values(schema.properties).some((field2) => field2["x-clack-visibility"] === "public")) errors.push(fail("/properties", "\uACF5\uAC1C \uB8E8\uD2B8 \uD0A4\uAC00 \uD558\uB098 \uC774\uC0C1 \uD544\uC694\uD569\uB2C8\uB2E4."));
2786
+ const note = (path, code, args) => errors.push({ path, message: OUTPUT_DATA_NOTES[code](args ?? {}), code, ...args ? { args } : {} });
2787
+ if (bytes(schema) > OUTPUT_DATA_LIMITS.schema_bytes) note("/", "schema_too_large");
2788
+ for (const name2 of schema.required ?? []) if (!Object.hasOwn(schema.properties, name2)) note("/required", "root_required_undeclared", { key: name2 });
2789
+ if (!Object.values(schema.properties).some((field2) => field2["x-clack-visibility"] === "public")) note("/properties", "public_root_key_missing");
2581
2790
  const defs = schema.$defs ?? {};
2582
- const resolve9 = (node) => typeof node?.$ref === "string" ? defs[node.$ref.slice("#/$defs/".length)] : node;
2791
+ const resolve10 = (node) => typeof node?.$ref === "string" ? defs[node.$ref.slice("#/$defs/".length)] : node;
2583
2792
  let properties = 0;
2584
2793
  const suggestRoots = [];
2585
2794
  const assetPointers = [];
2586
2795
  walkDataSchema(schema, (node, info) => {
2587
2796
  const at = info.pointer || "/";
2588
2797
  if (info.cycle) {
2589
- errors.push(fail(at, "\uC21C\uD658 \uCC38\uC870\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."));
2798
+ note(at, "ref_cycle");
2590
2799
  return;
2591
2800
  }
2592
2801
  if (info.unresolved) {
2593
- errors.push(fail(at, "\uB85C\uCEEC $defs \uCC38\uC870\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4."));
2802
+ note(at, "ref_not_local");
2594
2803
  return;
2595
2804
  }
2596
- if (info.depth > OUTPUT_DATA_LIMITS.depth) errors.push(fail(at, "\uB370\uC774\uD130 \uBB38\uC11C \uAE4A\uC774\uB294 \uB8E8\uD2B8 \uC544\uB798 6 \uC774\uD558\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4."));
2805
+ if (info.depth > OUTPUT_DATA_LIMITS.depth) note(at, "depth_over");
2597
2806
  if (info.depth === 0) return;
2598
2807
  if (typeof node.$ref === "string") {
2599
- if (Object.keys(node).some((key) => !["$ref", "title", "description", "x-clack-i18n", "x-clack-visibility"].includes(key))) errors.push(fail(at, "$ref\uC5D0\uB294 \uC124\uBA85 \uD0A4\uB9CC \uD568\uAED8 \uB458 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
2808
+ if (Object.keys(node).some((key) => !["$ref", "title", "description", "x-clack-i18n", "x-clack-visibility"].includes(key))) note(at, "ref_extra_keys");
2600
2809
  return;
2601
2810
  }
2602
2811
  if (!info.branch) properties++;
2603
2812
  if (Array.isArray(node.oneOf)) {
2604
2813
  const discriminator = node["x-clack-discriminator"];
2605
- if (!discriminator) errors.push(fail(at, "oneOf\uC5D0\uB294 x-clack-discriminator\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
2606
- if (node.type || node.properties || node.items || node.enum || Object.hasOwn(node, "const")) errors.push(fail(at, "oneOf \uB178\uB4DC\uC5D0\uB294 \uD615\uC2DD \uD0A4\uB97C \uD568\uAED8 \uB458 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
2814
+ if (!discriminator) note(at, "one_of_discriminator_missing");
2815
+ if (node.type || node.properties || node.items || node.enum || Object.hasOwn(node, "const")) note(at, "one_of_typed_keys");
2607
2816
  const seen = /* @__PURE__ */ new Set();
2608
2817
  for (const raw of node.oneOf) {
2609
- const branch = resolve9(raw);
2818
+ const branch = resolve10(raw);
2610
2819
  const tag = branch?.properties?.[discriminator];
2611
2820
  if (branch?.type !== "object" || typeof tag?.const !== "string" || tag.type !== "string" || !(branch.required ?? []).includes(discriminator)) {
2612
- errors.push(fail(at, "\uAC01 oneOf \uBD84\uAE30\uB294 \uD310\uBCC4\uC790 \uBB38\uC790\uC5F4 const\uB97C \uD544\uC218\uB85C \uAC00\uC9C4 \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4."));
2613
- } else if (seen.has(tag.const)) errors.push(fail(at, `\uD310\uBCC4\uC790 \uAC12 ${tag.const}\uC774 \uC911\uBCF5\uB429\uB2C8\uB2E4.`));
2821
+ note(at, "one_of_branch_shape");
2822
+ } else if (seen.has(tag.const)) note(at, "discriminator_duplicate", { value: tag.const });
2614
2823
  else seen.add(tag.const);
2615
2824
  }
2616
2825
  return;
2617
2826
  }
2618
- if (node["x-clack-discriminator"] !== void 0) errors.push(fail(at, "x-clack-discriminator\uB294 oneOf\uC5D0\uB9CC \uB461\uB2C8\uB2E4."));
2827
+ if (node["x-clack-discriminator"] !== void 0) note(at, "discriminator_misplaced");
2619
2828
  if (!node.type) {
2620
- errors.push(fail(at, "type\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."));
2829
+ note(at, "type_missing");
2621
2830
  return;
2622
2831
  }
2623
- for (const key of Object.keys(node)) if (TYPED_KEYS.has(key) && !SCALAR_KEYS[node.type].includes(key)) errors.push(fail(`${at}/${key}`, `${node.type}\uC5D0 \uC4F8 \uC218 \uC5C6\uB294 \uD0A4\uC785\uB2C8\uB2E4.`));
2624
- const range = (low, high, label) => {
2625
- if (typeof node[low] === "number" && typeof node[high] === "number" && node[low] > node[high]) errors.push(fail(at, `${label} \uBC94\uC704\uAC00 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`));
2832
+ for (const key of Object.keys(node)) if (TYPED_KEYS.has(key) && !SCALAR_KEYS[node.type].includes(key)) note(`${at}/${key}`, "key_not_for_type", { type: node.type });
2833
+ const range = (low, high, kind) => {
2834
+ if (typeof node[low] === "number" && typeof node[high] === "number" && node[low] > node[high]) note(at, "range_invalid", { range: kind });
2626
2835
  };
2627
- range("minLength", "maxLength", "\uAE38\uC774");
2628
- range("minItems", "maxItems", "\uD56D\uBAA9 \uC218");
2629
- range("minimum", "maximum", "\uAC12");
2630
- if (node.type === "string" && typeof node.maxLength !== "number") errors.push(fail(at, "\uBB38\uC790\uC5F4 maxLength\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
2631
- if (node.type === "array" && (!node.items || typeof node.maxItems !== "number")) errors.push(fail(at, "\uBC30\uC5F4\uC5D0\uB294 items\uC640 maxItems\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
2836
+ range("minLength", "maxLength", "length");
2837
+ range("minItems", "maxItems", "items");
2838
+ range("minimum", "maximum", "value");
2839
+ if (node.type === "string" && typeof node.maxLength !== "number") note(at, "string_max_length_missing");
2840
+ if (node.type === "array" && (!node.items || typeof node.maxItems !== "number")) note(at, "array_items_missing");
2632
2841
  if (node.type === "array" && node.uniqueItems === true) {
2633
2842
  let items = node.items;
2634
- for (let hops = 0; typeof items?.$ref === "string" && hops < 64; hops++) items = resolve9(items);
2635
- if (!items || Array.isArray(items.oneOf) || !SCALAR_TYPES.has(items.type)) errors.push(fail(`${at}/uniqueItems`, "uniqueItems\uB294 \uC2A4\uCE7C\uB77C \uBC30\uC5F4\uC5D0\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
2843
+ for (let hops = 0; typeof items?.$ref === "string" && hops < 64; hops++) items = resolve10(items);
2844
+ if (!items || Array.isArray(items.oneOf) || !SCALAR_TYPES.has(items.type)) note(`${at}/uniqueItems`, "unique_items_not_scalar");
2636
2845
  }
2637
2846
  if (node.type === "object") {
2638
- if (node.additionalProperties !== false || !node.properties || !Object.keys(node.properties).length) errors.push(fail(at, "\uAC1D\uCCB4\uC5D0\uB294 properties\uC640 additionalProperties: false\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
2639
- for (const name2 of node.required ?? []) if (!Object.hasOwn(node.properties ?? {}, name2)) errors.push(fail(at, `\uC120\uC5B8\uD558\uC9C0 \uC54A\uC740 \uD0A4 ${name2}\uC744 required\uC5D0 \uB450\uC5C8\uC2B5\uB2C8\uB2E4.`));
2847
+ if (node.additionalProperties !== false || !node.properties || !Object.keys(node.properties).length) note(at, "object_shape");
2848
+ for (const name2 of node.required ?? []) if (!Object.hasOwn(node.properties ?? {}, name2)) note(at, "required_undeclared", { key: name2 });
2640
2849
  }
2641
- for (const value2 of node.enum ?? []) if (typeof value2 !== (node.type === "integer" ? "number" : node.type)) errors.push(fail(at, "enum \uAC12\uC758 \uD615\uC2DD\uC774 type\uACFC \uB2E4\uB985\uB2C8\uB2E4."));
2850
+ for (const value2 of node.enum ?? []) if (typeof value2 !== (node.type === "integer" ? "number" : node.type)) note(at, "enum_type_mismatch");
2642
2851
  if (node["x-clack-asset"]) {
2643
2852
  assetPointers.push(at);
2644
- if (node.type !== "string" || node.format !== "uuid" || node.maxLength !== 36 || node.enum) errors.push(fail(at, "\uC790\uC0B0 \uB178\uB4DC\uB294 {type:string, format:uuid, maxLength:36}\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4."));
2645
- if (info.visibility === "private") errors.push(fail(at, "\uBE44\uACF5\uAC1C \uD0A4\uC5D0\uB294 \uC790\uC0B0\uC744 \uB458 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
2646
- } else if (node.format === "uuid" && node.type !== "string") errors.push(fail(at, "format\uC740 \uBB38\uC790\uC5F4\uC5D0\uB9CC \uC501\uB2C8\uB2E4."));
2853
+ if (node.type !== "string" || node.format !== "uuid" || node.maxLength !== 36 || node.enum) note(at, "asset_node_shape");
2854
+ if (info.visibility === "private") note(at, "asset_in_private");
2855
+ } else if (node.format === "uuid" && node.type !== "string") note(at, "format_not_string");
2647
2856
  if (node["x-clack-ai"]?.suggest === true) {
2648
- if (info.visibility !== "public") errors.push(fail(at, "AI \uC81C\uC548\uC740 \uACF5\uAC1C \uD0A4\uC5D0\uB9CC \uCF24 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
2857
+ if (info.visibility !== "public") note(at, "ai_suggest_private");
2649
2858
  suggestRoots.push(at);
2650
2859
  }
2651
2860
  });
2652
- if (properties > OUTPUT_DATA_LIMITS.properties) errors.push(fail("/", "\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8 \uC18D\uC131\uC740 400\uAC1C \uC774\uD558\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4."));
2653
- for (const root of suggestRoots) if (assetPointers.some((asset) => asset === root || asset.startsWith(`${root}/`))) errors.push(fail(root, "AI \uC81C\uC548 \uD558\uC704 \uD2B8\uB9AC\uC5D0 \uC790\uC0B0 \uB178\uB4DC\uB97C \uB458 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
2861
+ if (properties > OUTPUT_DATA_LIMITS.properties) note("/", "properties_over");
2862
+ for (const root of suggestRoots) if (assetPointers.some((asset) => asset === root || asset.startsWith(`${root}/`))) note(root, "ai_suggest_has_asset");
2654
2863
  return errors;
2655
2864
  }
2656
2865
  var ZWJ_AFTER = new RegExp("\\p{Extended_Pictographic}", "u");
@@ -3831,6 +4040,37 @@ var clack_skill_v1_schema_default = {
3831
4040
  minItems: 1,
3832
4041
  maxItems: 8,
3833
4042
  uniqueItems: true
4043
+ },
4044
+ sections: {
4045
+ type: "object",
4046
+ additionalProperties: false,
4047
+ description: "\uACF5\uAC1C \uBB38\uC790\uC5F4 \uD3FC \uD544\uB4DC\uB97C \uC18C\uAC1C \uC139\uC158\uC5D0 \uB9E4\uD551\uD569\uB2C8\uB2E4. \uD56D\uBAA9\uBCC4 \uCD5C\uB300 2,000\uC790, \uC120\uC5B8\uB41C \uD544\uB4DC \uCD5C\uB300 \uAE38\uC774 \uD569\uACC4 6,000\uC790\uC785\uB2C8\uB2E4.",
4048
+ properties: {
4049
+ how_to: {
4050
+ type: "string",
4051
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
4052
+ },
4053
+ notice: {
4054
+ type: "string",
4055
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
4056
+ },
4057
+ character: {
4058
+ type: "string",
4059
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
4060
+ },
4061
+ setting: {
4062
+ type: "string",
4063
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
4064
+ },
4065
+ relationship: {
4066
+ type: "string",
4067
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
4068
+ },
4069
+ first_scene: {
4070
+ type: "string",
4071
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
4072
+ }
4073
+ }
3834
4074
  }
3835
4075
  }
3836
4076
  }
@@ -5135,6 +5375,7 @@ var clack_content_v1_schema_default = {
5135
5375
  var MAX_ZIP = 10 * 1024 * 1024;
5136
5376
  var MAX_EXPANDED = 30 * 1024 * 1024;
5137
5377
  var MAX_FILES2 = 500;
5378
+ var MAX_ZIP_ENTRIES = 2e3;
5138
5379
  var textDecoder2 = new TextDecoder("utf-8", { fatal: true });
5139
5380
  var ajv = new Ajv20202({ allErrors: true, strict: false });
5140
5381
  ajv.addSchema(clack_content_v1_schema_default);
@@ -5179,6 +5420,14 @@ function skillPathAllowed(path) {
5179
5420
  if (parts.includes("scripts") || /\.(?:sh|bash|zsh|fish|bat|cmd|ps1|exe|dll|so|dylib|wasm|py|rb|php|jar)$/i.test(path)) return false;
5180
5421
  return true;
5181
5422
  }
5423
+ function isArchiveMetadata(path) {
5424
+ const parts = path.split("/");
5425
+ const name2 = parts.at(-1) ?? "";
5426
+ return parts[0] === "__MACOSX" || name2 === ".DS_Store" || name2.startsWith("._");
5427
+ }
5428
+ function unsafeArchivePath(path) {
5429
+ return path.startsWith("/") || /[\p{Cc}]/u.test(path) || path.split("/").includes("..");
5430
+ }
5182
5431
  async function collectFiles(directory) {
5183
5432
  const files = {};
5184
5433
  let totalBytes = 0;
@@ -5186,6 +5435,7 @@ async function collectFiles(directory) {
5186
5435
  const entries = await readdir(join2(directory, relative), { withFileTypes: true });
5187
5436
  for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
5188
5437
  const path = relative ? `${relative}/${entry.name}` : entry.name;
5438
+ if (isArchiveMetadata(path)) continue;
5189
5439
  if (!skillPathAllowed(path)) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD328\uD0A4\uC9C0 \uACBD\uB85C: ${path}`);
5190
5440
  if (entry.isSymbolicLink() || !(entry.isDirectory() || entry.isFile())) invalid2(`\uC77C\uBC18 \uD30C\uC77C\xB7\uD3F4\uB354\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4: ${path}`);
5191
5441
  if (entry.isDirectory()) {
@@ -5204,22 +5454,47 @@ async function collectFiles(directory) {
5204
5454
  }
5205
5455
  function inspectZip(bytes2, expectedDirectory, hashMismatch) {
5206
5456
  if (!bytes2.length || bytes2.length > MAX_ZIP) invalid2("\uC2A4\uD0AC ZIP\uC740 \uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC740 10 MiB \uC774\uD558 \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
5457
+ const raw = [];
5458
+ try {
5459
+ unzipSync2(bytes2, { filter(entry) {
5460
+ if (raw.length >= MAX_ZIP_ENTRIES) invalid2("ZIP \uD56D\uBAA9 \uC218\uAC00 2,000\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
5461
+ const dir = entry.name.endsWith("/");
5462
+ raw.push({ name: dir ? entry.name.slice(0, -1) : entry.name, dir });
5463
+ return false;
5464
+ } });
5465
+ } catch (error2) {
5466
+ if (error2 instanceof CliError) throw error2;
5467
+ return invalid2("ZIP \uAD6C\uC870\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5468
+ }
5469
+ for (const { name: name2 } of raw) if (unsafeArchivePath(name2)) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${name2.replace(new RegExp("\\p{C}", "gu"), "").slice(0, 160)}`);
5470
+ const kept = raw.filter(({ name: name2 }) => !isArchiveMetadata(name2));
5471
+ const keptFiles = kept.filter(({ dir }) => !dir);
5472
+ if (keptFiles.length > MAX_FILES2) invalid2("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
5473
+ let prefix = "";
5474
+ const top = keptFiles[0]?.name.split("/")[0];
5475
+ if (top && keptFiles.every(({ name: name2 }) => name2.startsWith(`${top}/`))) {
5476
+ const names = new Set(keptFiles.map(({ name: name2 }) => name2));
5477
+ if (names.has(`${top}/SKILL.md`) && names.has(`${top}/clack.skill.json`)) prefix = `${top}/`;
5478
+ }
5207
5479
  const seen = /* @__PURE__ */ new Set();
5208
- let entries = 0;
5480
+ const wanted = /* @__PURE__ */ new Map();
5481
+ for (const { name: name2, dir } of kept) {
5482
+ if (dir && prefix && name2 === prefix.slice(0, -1)) continue;
5483
+ const path = prefix && name2.startsWith(prefix) ? name2.slice(prefix.length) : name2;
5484
+ if (!skillPathAllowed(path) || seen.has(path.toLowerCase())) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${path}`);
5485
+ seen.add(path.toLowerCase());
5486
+ if (!dir) wanted.set(name2, path);
5487
+ }
5209
5488
  let totalBytes = 0;
5210
- let extracted;
5489
+ const extracted = {};
5211
5490
  try {
5212
- extracted = unzipSync2(bytes2, { filter(entry) {
5213
- entries++;
5214
- if (entries > MAX_FILES2) invalid2("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
5215
- const path = entry.name.endsWith("/") ? entry.name.slice(0, -1) : entry.name;
5216
- if (!skillPathAllowed(path) || seen.has(path.toLowerCase())) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${path}`);
5217
- seen.add(path.toLowerCase());
5218
- if (entry.name.endsWith("/")) return false;
5491
+ const content = unzipSync2(bytes2, { filter(entry) {
5492
+ if (!wanted.has(entry.name)) return false;
5219
5493
  totalBytes += entry.originalSize;
5220
5494
  if (totalBytes > MAX_EXPANDED) invalid2("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
5221
5495
  return true;
5222
5496
  } });
5497
+ for (const [name2, data] of Object.entries(content)) extracted[wanted.get(name2)] = data;
5223
5498
  } catch (error2) {
5224
5499
  if (error2 instanceof CliError) throw error2;
5225
5500
  return invalid2("ZIP \uAD6C\uC870\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
@@ -5322,7 +5597,7 @@ function fillEditorHash(files) {
5322
5597
  return new TextEncoder().encode(next);
5323
5598
  }
5324
5599
  async function prepareSkillPackage(source, options2 = {}) {
5325
- const path = resolve7(source);
5600
+ const path = resolve8(source);
5326
5601
  let info;
5327
5602
  try {
5328
5603
  info = await lstat4(path);
@@ -5347,34 +5622,34 @@ async function prepareSkillPackage(source, options2 = {}) {
5347
5622
  else return invalid2("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.");
5348
5623
  const inspected = inspectZip(
5349
5624
  bytes2,
5350
- info.isDirectory() ? basename4(path) : void 0,
5625
+ info.isDirectory() ? basename5(path) : void 0,
5351
5626
  info.isDirectory() && !options2.fillEditorHash ? "warn" : "error"
5352
5627
  );
5353
5628
  return { bytes: bytes2, sha256: hash(bytes2), ...inspected, ...filledManifest ? { filled_manifest: filledManifest } : {} };
5354
5629
  }
5355
5630
 
5356
5631
  // src/commands/skill.ts
5357
- var uuid2 = z9.uuid();
5358
- var createdSchema = z9.object({ id: uuid2, slug: z9.string(), type: z9.string() });
5359
- var uploadSchema = z9.object({
5632
+ var uuid2 = z10.uuid();
5633
+ var createdSchema = z10.object({ id: uuid2, slug: z10.string(), type: z10.string() });
5634
+ var uploadSchema = z10.object({
5360
5635
  version_id: uuid2,
5361
- upload_url: z9.url(),
5362
- headers: z9.record(z9.string(), z9.string()),
5363
- expires_at: z9.string(),
5364
- max_upload_bytes: z9.number().int().positive()
5636
+ upload_url: z10.url(),
5637
+ headers: z10.record(z10.string(), z10.string()),
5638
+ expires_at: z10.string(),
5639
+ max_upload_bytes: z10.number().int().positive()
5365
5640
  });
5366
- var versionSchema = z9.object({ id: uuid2, skill_id: uuid2, version: z9.string(), review_status: z9.string() });
5367
- var skillSlug = z9.string().regex(/^[a-z][a-z0-9-]{0,63}$/);
5368
- var semver = z9.string().regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/);
5369
- var category = z9.enum(["character_chat", "quiz", "worldbuilding", "gallery", "story", "agent_tool"]);
5370
- var visibility = z9.enum(["private", "unlisted", "public"]);
5371
- var managedVersionSchema = z9.object({
5641
+ var versionSchema = z10.object({ id: uuid2, skill_id: uuid2, version: z10.string(), review_status: z10.string() });
5642
+ var skillSlug = z10.string().regex(/^[a-z][a-z0-9-]{0,63}$/);
5643
+ var semver = z10.string().regex(/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/);
5644
+ var category = z10.enum(["character_chat", "quiz", "worldbuilding", "gallery", "story", "agent_tool"]);
5645
+ var visibility = z10.enum(["private", "unlisted", "public"]);
5646
+ var managedVersionSchema = z10.object({
5372
5647
  id: uuid2,
5373
5648
  skill_id: uuid2,
5374
- version: z9.string(),
5375
- review_status: z9.string(),
5376
- release_status: z9.string(),
5377
- package_hash: z9.string().nullable()
5649
+ version: z10.string(),
5650
+ review_status: z10.string(),
5651
+ release_status: z10.string(),
5652
+ package_hash: z10.string().nullable()
5378
5653
  });
5379
5654
  function serverData(schema, value2) {
5380
5655
  const parsed = schema.safeParse(value2);
@@ -5462,18 +5737,19 @@ async function pushSkillVersion(client, prepared, skillId) {
5462
5737
  if (error2 instanceof CliError && error2.code === "SKILL_VERSION_MAJOR_REQUIRED") {
5463
5738
  throw new CliError(`${error2.message} (\uCDE8\uC18C: clack skill cancel ${resolvedSkillId} ${session2.version_id})`, error2.code, error2.status);
5464
5739
  }
5465
- if (error2 instanceof CliError) throw new CliError(
5466
- `${error2.message} \uC644\uB8CC \uC7AC\uC2DC\uB3C4: clack skill complete ${resolvedSkillId} ${session2.version_id}`,
5467
- error2.code,
5468
- error2.status
5469
- );
5740
+ if (error2 instanceof CliError) {
5741
+ const judged = error2.status >= 400 && error2.status < 500 && error2.status !== 429;
5742
+ const hint = judged ? "\uD328\uD0A4\uC9C0\uB97C \uACE0\uCE5C \uB4A4 \uAC19\uC740 \uBC84\uC804\uC73C\uB85C \uB2E4\uC2DC clack skill push\uD558\uC138\uC694." : `\uC644\uB8CC \uC7AC\uC2DC\uB3C4: clack skill complete ${resolvedSkillId} ${session2.version_id}`;
5743
+ throw new CliError(`${error2.message}
5744
+ ${hint}`, error2.code, error2.status, error2.retryAfter);
5745
+ }
5470
5746
  throw error2;
5471
5747
  }
5472
5748
  }
5473
5749
  function registerSkillCommands(program2, runtime2) {
5474
5750
  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)");
5475
5751
  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) => {
5476
- const query = opts.query === void 0 ? void 0 : z9.string().trim().min(1).max(100).safeParse(opts.query);
5752
+ const query = opts.query === void 0 ? void 0 : z10.string().trim().min(1).max(100).safeParse(opts.query);
5477
5753
  if (query && !query.success) throw new CliError("\uAC80\uC0C9\uC5B4\uB294 1~100\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
5478
5754
  const selectedCategory = opts.category === void 0 ? void 0 : category.safeParse(opts.category);
5479
5755
  if (selectedCategory && !selectedCategory.success) throw new CliError("\uC2A4\uD0AC \uBD84\uB958\uB97C \uD655\uC778\uD558\uC138\uC694.");
@@ -5548,11 +5824,11 @@ function registerSkillCommands(program2, runtime2) {
5548
5824
  runtime2.action(skill.command("pack [dir]").description("\uC2A4\uD0AC \uD328\uD0A4\uC9C0 ZIP \uB9CC\uB4E4\uAE30 (\uB85C\uADF8\uC778 \uBD88\uD544\uC694, \uC5D0\uB514\uD130 \uC2A4\uD0AC\uC740 authoring.editor.sha256\uC744 \uCC44\uC6C0)").option("-o, --out <file>", "ZIP \uC800\uC7A5 \uACBD\uB85C (\uAE30\uBCF8: <\uC774\uB984>-<\uBC84\uC804>.zip)").option("--write-manifest", "\uCC44\uC6B4 sha256\uC744 \uB514\uB809\uD130\uB9AC\uC758 clack.skill.json\uC5D0\uB3C4 \uAE30\uB85D"), async (ctx, [dir], opts) => {
5549
5825
  const source = dir ?? ".";
5550
5826
  const prepared = await prepareSkillPackage(source, { fillEditorHash: true });
5551
- const out = resolve8(opts.out ?? `${prepared.manifest.name}-${prepared.manifest.version}.zip`);
5827
+ const out = resolve9(opts.out ?? `${prepared.manifest.name}-${prepared.manifest.version}.zip`);
5552
5828
  const writeManifest = Boolean(opts.writeManifest && prepared.filled_manifest);
5553
5829
  if (!ctx.options.dryRun) {
5554
5830
  await writeFile2(out, prepared.bytes);
5555
- if (writeManifest) await writeFile2(join3(resolve8(source), "clack.skill.json"), prepared.filled_manifest);
5831
+ if (writeManifest) await writeFile2(join3(resolve9(source), "clack.skill.json"), prepared.filled_manifest);
5556
5832
  }
5557
5833
  ctx.output({
5558
5834
  ...ctx.options.dryRun ? { dry_run: true } : {},
@@ -5582,7 +5858,7 @@ ${SDK_LOCATION_HELP}
5582
5858
  ]) {
5583
5859
  if (value2 === void 0) continue;
5584
5860
  if ((flag === "host-port" || flag === "editor-port") && !/^\d{2,5}$/.test(value2)) throw new CliError("\uD3EC\uD2B8\uB294 \uC22B\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
5585
- args.push(`--${flag}`, flag.endsWith("port") ? value2 : resolve8(value2));
5861
+ args.push(`--${flag}`, flag.endsWith("port") ? value2 : resolve9(value2));
5586
5862
  }
5587
5863
  const sdkDir = resolveSdkDir(opts.sdkDir);
5588
5864
  const serve = join3(sdkDir, "mock-host/serve.mjs");
@@ -5590,10 +5866,10 @@ ${SDK_LOCATION_HELP}
5590
5866
  ctx.output({ dry_run: true, sdk_dir: sdkDir, command: ["node", serve, ...args] });
5591
5867
  return;
5592
5868
  }
5593
- await new Promise((done, fail2) => {
5869
+ await new Promise((done, fail) => {
5594
5870
  const child = spawn(process.execPath, [serve, ...args], { stdio: "inherit" });
5595
- child.on("error", () => fail2(new CliError("\uBAA8\uC758 \uD638\uC2A4\uD2B8\uB97C \uC2E4\uD589\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", "SKILL_EDITOR_DEV_FAILED")));
5596
- child.on("exit", (code) => code ? fail2(new CliError(`\uBAA8\uC758 \uD638\uC2A4\uD2B8\uAC00 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4(\uCF54\uB4DC ${code}). SDK\uC5D0\uC11C pnpm build\uB97C \uBA3C\uC800 \uC2E4\uD589\uD588\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.`, "SKILL_EDITOR_DEV_FAILED")) : done());
5871
+ child.on("error", () => fail(new CliError("\uBAA8\uC758 \uD638\uC2A4\uD2B8\uB97C \uC2E4\uD589\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.", "SKILL_EDITOR_DEV_FAILED")));
5872
+ child.on("exit", (code) => code ? fail(new CliError(`\uBAA8\uC758 \uD638\uC2A4\uD2B8\uAC00 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4(\uCF54\uB4DC ${code}). SDK\uC5D0\uC11C pnpm build\uB97C \uBA3C\uC800 \uC2E4\uD589\uD588\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694.`, "SKILL_EDITOR_DEV_FAILED")) : done());
5597
5873
  });
5598
5874
  }
5599
5875
  );
@@ -5722,14 +5998,14 @@ function editorReport(prepared) {
5722
5998
  var SDK_LOCATION_HELP = "SDK\uB294 \uC544\uC9C1 npm\uC5D0 \uACF5\uAC1C\uB418\uC9C0 \uC54A\uC544 CLI\uC5D0 \uD3EC\uD568\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. SDK \uC18C\uC2A4 \uB514\uB809\uD130\uB9AC(\uD074\uB799 \uB0B4\uBD80 \uC791\uC5C5\uC790\uB294 \uB0B4\uBD80 \uC800\uC7A5\uC18C\uC758 clack-skill-editor-sdk)\uC5D0\uC11C pnpm install && pnpm build\uB97C \uC2E4\uD589\uD55C \uB4A4 --sdk-dir <SDK \uB514\uB809\uD130\uB9AC>, CLACK_SKILL_EDITOR_SDK_DIR=<SDK \uB514\uB809\uD130\uB9AC>, CLACK_MONOREPO_DIR=<\uB0B4\uBD80 \uC800\uC7A5\uC18C> \uC911 \uD558\uB098\uB85C \uC704\uCE58\uB97C \uC9C0\uC815\uD558\uC138\uC694.";
5723
5999
  function resolveSdkDir(option) {
5724
6000
  const candidates = [];
5725
- if (option) candidates.push(resolve8(option));
6001
+ if (option) candidates.push(resolve9(option));
5726
6002
  else {
5727
- if (process.env.CLACK_SKILL_EDITOR_SDK_DIR) candidates.push(resolve8(process.env.CLACK_SKILL_EDITOR_SDK_DIR));
6003
+ if (process.env.CLACK_SKILL_EDITOR_SDK_DIR) candidates.push(resolve9(process.env.CLACK_SKILL_EDITOR_SDK_DIR));
5728
6004
  try {
5729
- candidates.push(resolve8(createRequire(join3(process.cwd(), "noop.js")).resolve("@clack/skill-editor-sdk/package.json"), ".."));
6005
+ candidates.push(resolve9(createRequire(join3(process.cwd(), "noop.js")).resolve("@clack/skill-editor-sdk/package.json"), ".."));
5730
6006
  } catch {
5731
6007
  }
5732
- if (process.env.CLACK_MONOREPO_DIR) candidates.push(resolve8(process.env.CLACK_MONOREPO_DIR, "clack-skill-editor-sdk"));
6008
+ if (process.env.CLACK_MONOREPO_DIR) candidates.push(resolve9(process.env.CLACK_MONOREPO_DIR, "clack-skill-editor-sdk"));
5733
6009
  }
5734
6010
  const found = candidates.find((dir) => existsSync(join3(dir, "mock-host/serve.mjs")));
5735
6011
  if (!found) throw new CliError(`\uC2A4\uD0AC \uC5D0\uB514\uD130 SDK(@clack/skill-editor-sdk)\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. ${SDK_LOCATION_HELP}`, "SKILL_EDITOR_SDK_NOT_FOUND");
@@ -5745,24 +6021,24 @@ function skillVersionIds(skillId, versionId) {
5745
6021
  // src/lib/platform-mcp.ts
5746
6022
  var protocolVersions = /* @__PURE__ */ new Set(["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"]);
5747
6023
  var maxLineBytes = 1024 * 1024;
5748
- var collectionKey = z10.string().regex(/^[A-Za-z0-9_-]{1,64}$/);
6024
+ var collectionKey = z11.string().regex(/^[A-Za-z0-9_-]{1,64}$/);
5749
6025
  var patSchemas = {
5750
- skillList: z10.object({ q: z10.string().trim().min(1).max(100).optional() }).strict(),
5751
- skillGet: z10.object({ slug: z10.string().regex(/^[a-z][a-z0-9-]{0,63}$/) }).strict(),
5752
- skillPush: z10.object({ dir: z10.string().min(1).max(4096).optional(), skill_id: z10.uuid().optional() }).strict(),
5753
- skillVersionRef: z10.object({ skill_id: z10.uuid(), version_id: z10.uuid() }).strict(),
5754
- skillSubmit: z10.object({ skill_id: z10.uuid(), version_id: z10.uuid(), confirm: z10.boolean().optional() }).strict(),
5755
- skillRelease: z10.object({
5756
- skill_id: z10.uuid(),
5757
- version_id: z10.uuid(),
5758
- visibility: z10.enum(["private", "unlisted", "public"]),
5759
- confirm: z10.boolean().optional()
6026
+ skillList: z11.object({ q: z11.string().trim().min(1).max(100).optional() }).strict(),
6027
+ skillGet: z11.object({ slug: z11.string().regex(/^[a-z][a-z0-9-]{0,63}$/) }).strict(),
6028
+ skillPush: z11.object({ dir: z11.string().min(1).max(4096).optional(), skill_id: z11.uuid().optional() }).strict(),
6029
+ skillVersionRef: z11.object({ skill_id: z11.uuid(), version_id: z11.uuid() }).strict(),
6030
+ skillSubmit: z11.object({ skill_id: z11.uuid(), version_id: z11.uuid(), confirm: z11.boolean().optional() }).strict(),
6031
+ skillRelease: z11.object({
6032
+ skill_id: z11.uuid(),
6033
+ version_id: z11.uuid(),
6034
+ visibility: z11.enum(["private", "unlisted", "public"]),
6035
+ confirm: z11.boolean().optional()
5760
6036
  }).strict(),
5761
- contentPublish: z10.object({ content_id: z10.uuid(), version_id: z10.uuid(), confirm: z10.boolean().optional() }).strict(),
5762
- sharedCollections: z10.object({ content_id: z10.uuid() }).strict(),
5763
- sharedList: z10.object({ content_id: z10.uuid(), collection: collectionKey, limit: z10.number().int().min(1).max(100).optional() }).strict(),
5764
- sharedDocument: z10.object({ content_id: z10.uuid(), collection: collectionKey, key: collectionKey }).strict(),
5765
- sharedModerate: z10.object({ content_id: z10.uuid(), collection: collectionKey, key: collectionKey, confirm: z10.boolean().optional() }).strict()
6037
+ contentPublish: z11.object({ content_id: z11.uuid(), version_id: z11.uuid(), confirm: z11.boolean().optional() }).strict(),
6038
+ sharedCollections: z11.object({ content_id: z11.uuid() }).strict(),
6039
+ sharedList: z11.object({ content_id: z11.uuid(), collection: collectionKey, limit: z11.number().int().min(1).max(100).optional() }).strict(),
6040
+ sharedDocument: z11.object({ content_id: z11.uuid(), collection: collectionKey, key: collectionKey }).strict(),
6041
+ sharedModerate: z11.object({ content_id: z11.uuid(), collection: collectionKey, key: collectionKey, confirm: z11.boolean().optional() }).strict()
5766
6042
  };
5767
6043
  var patToolNames = /* @__PURE__ */ new Set([
5768
6044
  "platform_authoring",
@@ -5792,120 +6068,120 @@ function parsePat(schema, input, label) {
5792
6068
  return parsed.data;
5793
6069
  }
5794
6070
  var toolDefinitions = [
5795
- { 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\xB7\uC644\uB8CC\xB7\uD3EC\uAE30\uB97C \uCC98\uB9AC\uD569\uB2C8\uB2E4. complete/abandon\uC740 \uD3FC \uC138\uC158 \uC804\uC6A9\uC774\uBA70 complete\uB294 \uD604\uC7AC revision \uD328\uD0A4\uC9D5, abandon\uC740 confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC5D0\uB514\uD130\xB7\uC9C0\uCE68\uD615\uC740 \uC13C\uD130\uC5D0\uC11C \uC791\uC5C5\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: true } },
6071
+ { 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\xB7\uC644\uB8CC\xB7\uD3EC\uAE30\uB97C \uCC98\uB9AC\uD569\uB2C8\uB2E4. complete/abandon\uC740 \uD3FC \uC138\uC158 \uC804\uC6A9\uC774\uBA70 complete\uB294 \uD604\uC7AC revision \uD328\uD0A4\uC9D5, abandon\uC740 confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC5D0\uB514\uD130\xB7\uC9C0\uCE68\uD615\uC740 \uC13C\uD130\uC5D0\uC11C \uC791\uC5C5\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", ...z11.toJSONSchema(authoringInput) }, annotations: { readOnlyHint: false, destructiveHint: true } },
5796
6072
  {
5797
6073
  name: "platform_usage_get",
5798
6074
  description: "\uC11C\uBC84 \uD0A4\uAC00 \uC18D\uD55C \uCF58\uD150\uCE20\uC758 \uD50C\uB7AB\uD3FC \uC0AC\uC6A9\uB7C9\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
5799
- inputSchema: z10.toJSONSchema(platformReadSchemas.usage),
6075
+ inputSchema: z11.toJSONSchema(platformReadSchemas.usage),
5800
6076
  annotations: { readOnlyHint: true, destructiveHint: false }
5801
6077
  },
5802
6078
  {
5803
6079
  name: "platform_server_data_get",
5804
6080
  description: "\uC11C\uBC84 \uD0A4\uB85C \uCF58\uD150\uCE20 \uB370\uC774\uD130 \uBB38\uC11C\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. data:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
5805
- inputSchema: z10.toJSONSchema(platformReadSchemas.get),
6081
+ inputSchema: z11.toJSONSchema(platformReadSchemas.get),
5806
6082
  annotations: { readOnlyHint: true, destructiveHint: false }
5807
6083
  },
5808
6084
  {
5809
6085
  name: "platform_server_data_list",
5810
6086
  description: "\uC11C\uBC84 \uD0A4\uB85C \uCF58\uD150\uCE20 \uB370\uC774\uD130 \uBB38\uC11C \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. data:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
5811
- inputSchema: z10.toJSONSchema(platformReadSchemas.list),
6087
+ inputSchema: z11.toJSONSchema(platformReadSchemas.list),
5812
6088
  annotations: { readOnlyHint: true, destructiveHint: false }
5813
6089
  },
5814
6090
  {
5815
6091
  name: "platform_server_data_leaderboard",
5816
6092
  description: "\uC11C\uBC84 \uD0A4\uB85C \uACF5\uC720 \uB370\uC774\uD130 \uC21C\uC704\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. data:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
5817
- inputSchema: z10.toJSONSchema(platformReadSchemas.leaderboard),
6093
+ inputSchema: z11.toJSONSchema(platformReadSchemas.leaderboard),
5818
6094
  annotations: { readOnlyHint: true, destructiveHint: false }
5819
6095
  },
5820
6096
  {
5821
6097
  name: "platform_server_data_put",
5822
6098
  description: "\uC11C\uBC84 \uD0A4\uB85C \uB2E8\uC77C \uBB38\uC11C \uC804\uCCB4\uB97C \uC800\uC7A5\uD569\uB2C8\uB2E4. \uAC1C\uC815 \uBC88\uD638 \uB610\uB294 \uBD80\uC7AC \uC870\uAC74\uACFC confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
5823
- inputSchema: z10.toJSONSchema(platformReadSchemas.put),
6099
+ inputSchema: z11.toJSONSchema(platformReadSchemas.put),
5824
6100
  annotations: { readOnlyHint: false, destructiveHint: true }
5825
6101
  },
5826
6102
  {
5827
6103
  name: "platform_server_data_patch",
5828
6104
  description: "\uC11C\uBC84 \uD0A4\uB85C \uB2E8\uC77C \uBB38\uC11C\uB97C \uBCD1\uD569 \uC218\uC815\uD569\uB2C8\uB2E4. \uAC1C\uC815 \uBC88\uD638\uC640 confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
5829
- inputSchema: z10.toJSONSchema(platformReadSchemas.patch),
6105
+ inputSchema: z11.toJSONSchema(platformReadSchemas.patch),
5830
6106
  annotations: { readOnlyHint: false, destructiveHint: true }
5831
6107
  },
5832
6108
  {
5833
6109
  name: "platform_server_data_delete",
5834
6110
  description: "\uC11C\uBC84 \uD0A4\uB85C \uB2E8\uC77C \uBB38\uC11C\uB97C \uC0AD\uC81C\uD558\uAC70\uB098 \uC228\uAE41\uB2C8\uB2E4. confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
5835
- inputSchema: z10.toJSONSchema(platformReadSchemas.delete),
6111
+ inputSchema: z11.toJSONSchema(platformReadSchemas.delete),
5836
6112
  annotations: { readOnlyHint: false, destructiveHint: true }
5837
6113
  },
5838
6114
  // 아래는 개인 액세스 토큰(CLACK_TOKEN, pat_)으로 동작하는 개인 도구다. 서버 키 도구와 인증 평면이 다르다.
5839
6115
  {
5840
6116
  name: "platform_skill_list",
5841
6117
  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.",
5842
- inputSchema: z10.toJSONSchema(patSchemas.skillList),
6118
+ inputSchema: z11.toJSONSchema(patSchemas.skillList),
5843
6119
  annotations: { readOnlyHint: true, destructiveHint: false }
5844
6120
  },
5845
6121
  {
5846
6122
  name: "platform_skill_get",
5847
6123
  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.",
5848
- inputSchema: z10.toJSONSchema(patSchemas.skillGet),
6124
+ inputSchema: z11.toJSONSchema(patSchemas.skillGet),
5849
6125
  annotations: { readOnlyHint: true, destructiveHint: false }
5850
6126
  },
5851
6127
  {
5852
6128
  name: "platform_skill_push",
5853
6129
  description: "\uB85C\uCEEC \uC2A4\uD0AC \uB514\uB809\uD130\uB9AC\uB97C \uAC80\uC99D\uD558\uACE0 \uC0C8 \uBC84\uC804\uC73C\uB85C \uC5C5\uB85C\uB4DC\uD569\uB2C8\uB2E4. dir\uC744 \uC0DD\uB7B5\uD558\uBA74 \uD604\uC7AC \uB514\uB809\uD130\uB9AC\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4. skill:write \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
5854
- inputSchema: z10.toJSONSchema(patSchemas.skillPush),
6130
+ inputSchema: z11.toJSONSchema(patSchemas.skillPush),
5855
6131
  annotations: { readOnlyHint: false, destructiveHint: false }
5856
6132
  },
5857
6133
  {
5858
6134
  name: "platform_skill_status",
5859
6135
  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.",
5860
- inputSchema: z10.toJSONSchema(patSchemas.skillVersionRef),
6136
+ inputSchema: z11.toJSONSchema(patSchemas.skillVersionRef),
5861
6137
  annotations: { readOnlyHint: true, destructiveHint: false }
5862
6138
  },
5863
6139
  {
5864
6140
  name: "platform_skill_submit",
5865
6141
  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.",
5866
- inputSchema: z10.toJSONSchema(patSchemas.skillSubmit),
6142
+ inputSchema: z11.toJSONSchema(patSchemas.skillSubmit),
5867
6143
  annotations: { readOnlyHint: false, destructiveHint: true }
5868
6144
  },
5869
6145
  {
5870
6146
  name: "platform_skill_release",
5871
6147
  description: "\uC2B9\uC778\uB41C \uC2A4\uD0AC \uBC84\uC804\uC744 \uACF5\uAC1C \uBC94\uC704\uB85C \uAC8C\uC2DC\uD569\uB2C8\uB2E4. skill:publish \uAD8C\uD55C\uACFC confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
5872
- inputSchema: z10.toJSONSchema(patSchemas.skillRelease),
6148
+ inputSchema: z11.toJSONSchema(patSchemas.skillRelease),
5873
6149
  annotations: { readOnlyHint: false, destructiveHint: true }
5874
6150
  },
5875
6151
  {
5876
6152
  name: "platform_content_publish",
5877
6153
  description: "\uC2B9\uC778\uB41C \uB0B4 HTML \uCF58\uD150\uCE20 \uBC84\uC804\uC744 \uACF5\uAC1C\uD569\uB2C8\uB2E4. creator-content:publish \uAD8C\uD55C\uACFC confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
5878
- inputSchema: z10.toJSONSchema(patSchemas.contentPublish),
6154
+ inputSchema: z11.toJSONSchema(patSchemas.contentPublish),
5879
6155
  annotations: { readOnlyHint: false, destructiveHint: true }
5880
6156
  },
5881
6157
  {
5882
6158
  name: "platform_shared_collections_list",
5883
6159
  description: "\uB0B4 \uCF58\uD150\uCE20\uC758 \uC774\uC6A9\uC790 \uACF5\uAC1C(shared) \uBB38\uC11C \uCEEC\uB809\uC158 \uC774\uB984\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. platform:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
5884
- inputSchema: z10.toJSONSchema(patSchemas.sharedCollections),
6160
+ inputSchema: z11.toJSONSchema(patSchemas.sharedCollections),
5885
6161
  annotations: { readOnlyHint: true, destructiveHint: false }
5886
6162
  },
5887
6163
  {
5888
6164
  name: "platform_shared_documents_list",
5889
6165
  description: "\uB0B4 \uCF58\uD150\uCE20\uC758 \uACF5\uC720 \uBB38\uC11C \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. platform:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
5890
- inputSchema: z10.toJSONSchema(patSchemas.sharedList),
6166
+ inputSchema: z11.toJSONSchema(patSchemas.sharedList),
5891
6167
  annotations: { readOnlyHint: true, destructiveHint: false }
5892
6168
  },
5893
6169
  {
5894
6170
  name: "platform_shared_document_get",
5895
6171
  description: "\uB0B4 \uCF58\uD150\uCE20\uC758 \uACF5\uC720 \uBB38\uC11C \uD558\uB098\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. platform:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
5896
- inputSchema: z10.toJSONSchema(patSchemas.sharedDocument),
6172
+ inputSchema: z11.toJSONSchema(patSchemas.sharedDocument),
5897
6173
  annotations: { readOnlyHint: true, destructiveHint: false }
5898
6174
  },
5899
6175
  {
5900
6176
  name: "platform_shared_document_hide",
5901
6177
  description: "\uC774\uC6A9\uC790\uAC00 \uC4F4 \uACF5\uC720 \uBB38\uC11C\uB97C \uC228\uAE41\uB2C8\uB2E4. platform:write \uAD8C\uD55C\uACFC confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
5902
- inputSchema: z10.toJSONSchema(patSchemas.sharedModerate),
6178
+ inputSchema: z11.toJSONSchema(patSchemas.sharedModerate),
5903
6179
  annotations: { readOnlyHint: false, destructiveHint: true }
5904
6180
  },
5905
6181
  {
5906
6182
  name: "platform_shared_document_delete",
5907
6183
  description: "\uC774\uC6A9\uC790\uAC00 \uC4F4 \uACF5\uC720 \uBB38\uC11C\uB97C \uC601\uAD6C \uC0AD\uC81C\uD569\uB2C8\uB2E4. platform:write \uAD8C\uD55C\uACFC confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
5908
- inputSchema: z10.toJSONSchema(patSchemas.sharedModerate),
6184
+ inputSchema: z11.toJSONSchema(patSchemas.sharedModerate),
5909
6185
  annotations: { readOnlyHint: false, destructiveHint: true }
5910
6186
  }
5911
6187
  ];
@@ -6146,14 +6422,14 @@ function registerIntegrationCommands(program2, runtime2) {
6146
6422
 
6147
6423
  // src/commands/page.ts
6148
6424
  import { Option as Option3 } from "commander";
6149
- import { z as z11 } from "zod";
6150
- var uuid3 = (value2) => parse(z11.uuid(), value2);
6151
- var revision3 = (value2) => parse(z11.coerce.number().int().nonnegative().max(4294967295), value2);
6425
+ import { z as z12 } from "zod";
6426
+ var uuid3 = (value2) => parse(z12.uuid(), value2);
6427
+ var revision3 = (value2) => parse(z12.coerce.number().int().nonnegative().max(4294967295), value2);
6152
6428
  var base = "/v4/creator/custom-pages";
6153
- var presentationSchema = z11.object({
6154
- schema_version: z11.literal(1),
6155
- header_mode: z11.enum(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close", "floating_close_safe_area"]),
6156
- color_scheme: z11.enum(["light", "dark"])
6429
+ var presentationSchema = z12.object({
6430
+ schema_version: z12.literal(1),
6431
+ header_mode: z12.enum(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close", "floating_close_safe_area"]),
6432
+ color_scheme: z12.enum(["light", "dark"])
6157
6433
  }).strict();
6158
6434
  function registerPageCommands(program2, runtime2) {
6159
6435
  const page = program2.command("page").description("\uC138\uACC4\uAD00\xB7\uB0B4 \uACF5\uAC1C \uD648 HTML \uAFB8\uBBF8\uAE30 (custom-page:read\xB7write\xB7publish)");
@@ -6161,7 +6437,7 @@ function registerPageCommands(program2, runtime2) {
6161
6437
  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)}`)));
6162
6438
  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) => {
6163
6439
  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.");
6164
- const policy = parse(z11.literal("2026-09-22"), opts.policyVersion);
6440
+ const policy = parse(z12.literal("2026-09-22"), opts.policyVersion);
6165
6441
  await mutate(ctx, "POST", base, { target_type: opts.target, ...opts.target === "space" ? { space_id: uuid3(opts.spaceId) } : {}, policy_version: policy });
6166
6442
  });
6167
6443
  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", "floating_close_safe_area"]).default("fixed")).addOption(new Option3("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).default("light")), async (ctx, [id2, file], opts) => {
@@ -6226,7 +6502,8 @@ try {
6226
6502
  status: failure.status,
6227
6503
  code: failure.code,
6228
6504
  message: failure.message,
6229
- ...failure.retryAfter ? { retry_after: failure.retryAfter } : {}
6505
+ ...failure.retryAfter ? { retry_after: failure.retryAfter } : {},
6506
+ ...failure.recovery ? { recovery: failure.recovery } : {}
6230
6507
  });
6231
6508
  const json2 = program.opts().json || process.argv.includes("--json") || await store.resolve(program.opts(), true).then((value2) => value2.options.json).catch(() => false);
6232
6509
  if (json2) process.stdout.write(JSON.stringify(envelope) + "\n");