@clack-platform/cli 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +36 -6
  2. package/dist/cli.js +1405 -143
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -39,6 +39,9 @@ function apiError(status, body, retryAfter) {
39
39
  if (code === "SKILL_VERSION_MAJOR_REQUIRED") {
40
40
  message += " clack.skill.json\xB7SKILL.md\uC758 version\uC744 \uC62C\uB824 \uB2E4\uC2DC push\uD558\uC138\uC694. \uAC19\uC740 \uBC84\uC804\uC744 \uD638\uD658\uB418\uAC8C \uACE0\uCCD0 \uC62C\uB9AC\uB824\uBA74 \uBA3C\uC800 clack skill cancel\uB85C \uC774 \uBC84\uC804\uC744 \uC9C0\uC6B0\uC138\uC694.";
41
41
  }
42
+ if (code === "CONTENT_INFO_VERSION_DISABLED") message += " clack content config\uC758 info_version.enabled\uAC00 true\uC778 \uD658\uACBD\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.";
43
+ if (code === "CONTENT_INFO_UNCHANGED") message += " \uC815\uBCF4\uB97C \uBA3C\uC800 clack content update\uB85C \uC218\uC815\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.";
44
+ 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.";
42
45
  if (status === 423 || code === "USER_BANNED") message += " \uACC4\uC815 \uC81C\uD55C \uC0C1\uD0DC\uB97C \uC571\uC5D0\uC11C \uD655\uC778\uD558\uC138\uC694.";
43
46
  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.";
44
47
  const raw = Number(value2.retry_after ?? retryAfter);
@@ -150,7 +153,7 @@ function redact(value2) {
150
153
  // package.json
151
154
  var package_default = {
152
155
  name: "@clack-platform/cli",
153
- version: "0.1.1",
156
+ version: "0.3.0",
154
157
  description: "\uD074\uB799 \uACC4\uC815\uC758 \uC0C1\uD488\xB7\uCF58\uD150\uCE20\xB7\uD504\uB85C\uD544\uC744 \uAD00\uB9AC\uD558\uB294 CLI",
155
158
  type: "module",
156
159
  bin: {
@@ -399,9 +402,9 @@ function documentPath(collection2, key) {
399
402
  return `/platform/v1/data/${collection2}/docs/${key}`;
400
403
  }
401
404
  function bodySize(body) {
402
- const bytes = Buffer.byteLength(JSON.stringify(body), "utf8");
403
- if (bytes > maxBodyBytes) throw new CliError("\uBB38\uC11C \uBCF8\uBB38\uC740 64 KiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.", "VALIDATION_ERROR");
404
- return bytes;
405
+ const bytes2 = Buffer.byteLength(JSON.stringify(body), "utf8");
406
+ if (bytes2 > maxBodyBytes) throw new CliError("\uBB38\uC11C \uBCF8\uBB38\uC740 64 KiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.", "VALIDATION_ERROR");
407
+ return bytes2;
405
408
  }
406
409
  async function readPlatformBodyFile(file) {
407
410
  let raw;
@@ -598,11 +601,11 @@ async function authoringRequest(client, raw, surface = "cli") {
598
601
  if (input.action === "upload") {
599
602
  const stat3 = await lstat2(input.file);
600
603
  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.");
601
- const bytes = await readFile2(input.file);
604
+ const bytes2 = await readFile2(input.file);
602
605
  const body2 = new FormData();
603
606
  body2.append("field", input.field);
604
607
  if (input.item_index !== void 0) body2.append("item_index", String(input.item_index));
605
- body2.append("file", new Blob([bytes]), basename(input.file));
608
+ body2.append("file", new Blob([bytes2]), basename(input.file));
606
609
  return client.request("POST", `${path}/assets`, { body: body2 });
607
610
  }
608
611
  if (input.action === "image" || input.action === "image-batch") {
@@ -1262,9 +1265,9 @@ function isRemoteImage(value2) {
1262
1265
  return false;
1263
1266
  }
1264
1267
  }
1265
- function dimensions(bytes) {
1268
+ function dimensions(bytes2) {
1266
1269
  try {
1267
- const result = imageSize(bytes);
1270
+ const result = imageSize(bytes2);
1268
1271
  if (!result.width || !result.height || result.width > 2e4 || result.height > 2e4 || !result.type || !MIME[result.type]) throw new Error();
1269
1272
  return { width: result.width, height: result.height, mime: MIME[result.type] };
1270
1273
  } catch {
@@ -1291,26 +1294,26 @@ async function prepareImage(source, baseDir = process.cwd(), measureRemote = fal
1291
1294
  } finally {
1292
1295
  await reader.cancel();
1293
1296
  }
1294
- const bytes2 = new Uint8Array(size);
1297
+ const bytes3 = new Uint8Array(size);
1295
1298
  let offset = 0;
1296
1299
  for (const chunk of chunks) {
1297
- bytes2.set(chunk, offset);
1300
+ bytes3.set(chunk, offset);
1298
1301
  offset += chunk.length;
1299
1302
  }
1300
- return { source, ...dimensions(bytes2) };
1303
+ return { source, ...dimensions(bytes3) };
1301
1304
  }
1302
1305
  if (/^[a-z][a-z0-9+.-]*:/i.test(source) && !/^[a-z]:[\\/]/i.test(source)) throw new CliError("\uC774\uBBF8\uC9C0\uB294 \uB85C\uCEEC \uD30C\uC77C \uB610\uB294 http(s) URL\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
1303
1306
  const path = resolve(baseDir, source);
1304
- let bytes;
1307
+ let bytes2;
1305
1308
  try {
1306
1309
  const info = await stat(path);
1307
1310
  if (!info.isFile() || info.size === 0 || info.size > MAX_IMAGE_BYTES) throw new Error();
1308
- bytes = await readFile5(path);
1309
- if (bytes.length > MAX_IMAGE_BYTES) throw new Error();
1311
+ bytes2 = await readFile5(path);
1312
+ if (bytes2.length > MAX_IMAGE_BYTES) throw new Error();
1310
1313
  } catch {
1311
1314
  throw new CliError(`10MB \uC774\uD558 \uC774\uBBF8\uC9C0 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path}`);
1312
1315
  }
1313
- return { source: path, bytes, ...dimensions(bytes) };
1316
+ return { source: path, bytes: bytes2, ...dimensions(bytes2) };
1314
1317
  }
1315
1318
  async function uploadPrepared(ctx, image2) {
1316
1319
  if (!image2.bytes) return { originalUrl: image2.source };
@@ -1998,6 +2001,11 @@ import { createInterface } from "readline";
1998
2001
  import { z as z10 } from "zod";
1999
2002
 
2000
2003
  // src/commands/skill.ts
2004
+ import { spawn } from "child_process";
2005
+ import { existsSync } from "fs";
2006
+ import { writeFile as writeFile2 } from "fs/promises";
2007
+ import { createRequire } from "module";
2008
+ import { join as join3, resolve as resolve8 } from "path";
2001
2009
  import { z as z9 } from "zod";
2002
2010
 
2003
2011
  // src/commands/content.ts
@@ -2034,15 +2042,15 @@ var CAPABILITIES = /* @__PURE__ */ new Set([
2034
2042
  function isObject(value2) {
2035
2043
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
2036
2044
  }
2037
- function parseObject(bytes) {
2045
+ function parseObject(bytes2) {
2038
2046
  try {
2039
- const value2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
2047
+ const value2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
2040
2048
  return isObject(value2) ? value2 : null;
2041
2049
  } catch {
2042
2050
  return null;
2043
2051
  }
2044
2052
  }
2045
- function inspectContentPackage(bytes) {
2053
+ function inspectContentPackage(bytes2) {
2046
2054
  const warnings = [];
2047
2055
  const paths = /* @__PURE__ */ new Set();
2048
2056
  const profileFiles = /* @__PURE__ */ new Set();
@@ -2061,7 +2069,7 @@ function inspectContentPackage(bytes) {
2061
2069
  let tooLargeExpanded = false;
2062
2070
  let files;
2063
2071
  try {
2064
- files = unzipSync(bytes, { filter(entry) {
2072
+ files = unzipSync(bytes2, { filter(entry) {
2065
2073
  entries++;
2066
2074
  if (entries > MAX_FILES) {
2067
2075
  tooManyFiles = true;
@@ -2144,31 +2152,80 @@ var createSchema = z8.object({
2144
2152
  kind: z8.enum(["html", "gallery", "slideshow", "video"]).default("html"),
2145
2153
  policy_version: z8.literal("2026-09-22")
2146
2154
  }).strict();
2155
+ var METADATA_LANGS = ["ko", "en"];
2156
+ var TAG_PATTERN = /^[\p{L}\p{M}\p{N}_]{1,30}$/u;
2157
+ var localizedEntry = z8.object({
2158
+ title: z8.string().trim().max(120),
2159
+ description: z8.string().trim().max(2e3).optional()
2160
+ }).strict();
2161
+ var updateSchema = z8.object({
2162
+ title: z8.string().trim().max(120).optional(),
2163
+ description: z8.string().trim().max(2e3).optional(),
2164
+ kind: z8.enum(["html", "gallery", "slideshow", "video"]).optional(),
2165
+ thumbnail_id: z8.uuid().nullable().optional(),
2166
+ metadata_mode: z8.enum(["single", "localized"]).optional(),
2167
+ metadata_lang: z8.enum(METADATA_LANGS).nullable().optional(),
2168
+ localized_metadata: z8.partialRecord(z8.enum(METADATA_LANGS), localizedEntry).nullable().optional(),
2169
+ tags: z8.array(z8.string().max(64)).max(50).optional()
2170
+ }).strict().superRefine((value2, ctx) => {
2171
+ const issue = (message) => ctx.addIssue({ code: "custom", message });
2172
+ 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.");
2173
+ if (value2.tags) {
2174
+ const seen = /* @__PURE__ */ new Set();
2175
+ for (const raw of value2.tags) {
2176
+ const tag = raw.trim().replace(/^#+/, "").trim().normalize("NFC");
2177
+ if (!TAG_PATTERN.test(tag)) issue("\uD574\uC2DC\uD0DC\uADF8\uB294 \uAE00\uC790\xB7\uC22B\uC790\xB7\uBC11\uC904(_)\uB9CC \uC0AC\uC6A9\uD574 1~30\uC790\uB85C \uC785\uB825\uD558\uC138\uC694.");
2178
+ const key = tag.normalize("NFKC").toLowerCase();
2179
+ if (key.length > 40) issue("\uD574\uC2DC\uD0DC\uADF8\uAC00 \uB108\uBB34 \uAE41\uB2C8\uB2E4. \uB354 \uC9E7\uAC8C \uC785\uB825\uD558\uC138\uC694.");
2180
+ seen.add(key);
2181
+ }
2182
+ if (seen.size > 10) issue("\uD574\uC2DC\uD0DC\uADF8\uB294 \uCD5C\uB300 10\uAC1C\uAE4C\uC9C0 \uB4F1\uB85D\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
2183
+ }
2184
+ if (value2.metadata_mode === "localized") {
2185
+ if (!value2.metadata_lang) issue("\uC5B8\uC5B4\uBCC4\uB85C \uC785\uB825\uD558\uB824\uBA74 --metadata-lang\uC73C\uB85C \uB300\uD45C \uC5B8\uC5B4\uB97C \uC9C0\uC815\uD558\uC138\uC694.");
2186
+ if (!value2.localized_metadata) issue("\uC5B8\uC5B4\uBCC4\uB85C \uC785\uB825\uD558\uB824\uBA74 --localized-metadata\uB85C \uB300\uD45C \uC5B8\uC5B4 \uC678 \uC5B8\uC5B4\uB97C 1\uAC1C \uC774\uC0C1 \uC9C0\uC815\uD558\uC138\uC694.");
2187
+ }
2188
+ if (value2.localized_metadata) {
2189
+ for (const [lang, entry] of Object.entries(value2.localized_metadata)) {
2190
+ if (entry && !entry.title.trim()) issue("\uC120\uD0DD\uD55C \uBAA8\uB4E0 \uC5B8\uC5B4\uC758 \uC81C\uBAA9\uC744 \uC785\uB825\uD558\uC138\uC694.");
2191
+ if (lang === value2.metadata_lang) issue("\uB300\uD45C \uC5B8\uC5B4\uB294 --title\xB7--description\uC5D0 \uB123\uACE0, \uC5B8\uC5B4\uBCC4 \uAC12\uC5D0\uB294 \uB2E4\uB978 \uC5B8\uC5B4\uB9CC \uB123\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
2192
+ }
2193
+ if (value2.metadata_mode === "single" && Object.keys(value2.localized_metadata).length) issue("\uD55C \uC5B8\uC5B4(single)\uB85C \uC785\uB825\uD560 \uB54C\uB294 \uC5B8\uC5B4\uBCC4 \uAC12\uC744 \uBCF4\uB0BC \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2194
+ }
2195
+ });
2196
+ function jsonOption(name2, value2) {
2197
+ if (value2 === "none") return null;
2198
+ try {
2199
+ return JSON.parse(value2);
2200
+ } catch {
2201
+ throw new CliError(`${name2} \uAC12\uC740 JSON \uAC1D\uCCB4\uC774\uAC70\uB098 none\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.`);
2202
+ }
2203
+ }
2147
2204
  async function prepareContentFile(source) {
2148
2205
  const file = resolve6(source);
2149
2206
  const extension = extname(file).toLowerCase();
2150
2207
  if (![".html", ".htm", ".zip"].includes(extension)) throw new CliError("HTML \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.");
2151
- let bytes;
2208
+ let bytes2;
2152
2209
  try {
2153
2210
  const info = await stat2(file);
2154
2211
  if (!info.isFile() || info.size === 0 || info.size > MAX_BYTES) throw new Error();
2155
- bytes = await readFile8(file);
2156
- if (!bytes.length || bytes.length > MAX_BYTES) throw new Error();
2212
+ bytes2 = await readFile8(file);
2213
+ if (!bytes2.length || bytes2.length > MAX_BYTES) throw new Error();
2157
2214
  } catch {
2158
2215
  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.");
2159
2216
  }
2160
2217
  return {
2161
- bytes,
2162
- warnings: extension === ".zip" ? inspectContentPackage(bytes) : [],
2218
+ bytes: bytes2,
2219
+ warnings: extension === ".zip" ? inspectContentPackage(bytes2) : [],
2163
2220
  metadata: {
2164
2221
  filename: basename3(file),
2165
2222
  content_type: extension === ".zip" ? "application/zip" : "text/html",
2166
- byte_size: bytes.length,
2167
- sha256: createHash2("sha256").update(bytes).digest("hex")
2223
+ byte_size: bytes2.length,
2224
+ sha256: createHash2("sha256").update(bytes2).digest("hex")
2168
2225
  }
2169
2226
  };
2170
2227
  }
2171
- async function putContentFile(session2, bytes, fetcher = fetch) {
2228
+ async function putContentFile(session2, bytes2, fetcher = fetch) {
2172
2229
  uuid(session2.upload_id);
2173
2230
  let url;
2174
2231
  try {
@@ -2179,7 +2236,7 @@ async function putContentFile(session2, bytes, fetcher = fetch) {
2179
2236
  }
2180
2237
  const contentType = session2.headers?.["Content-Type"];
2181
2238
  const checksum = session2.headers?.["x-amz-checksum-sha256"];
2182
- if (!["text/html", "application/zip"].includes(contentType ?? "") || checksum !== createHash2("sha256").update(bytes).digest("base64")) {
2239
+ if (!["text/html", "application/zip"].includes(contentType ?? "") || checksum !== createHash2("sha256").update(bytes2).digest("base64")) {
2183
2240
  throw new CliError("\uC5C5\uB85C\uB4DC \uCCB4\uD06C\uC12C\uACFC \uD30C\uC77C \uD615\uC2DD\uC774 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_UPLOAD_RESPONSE", 502);
2184
2241
  }
2185
2242
  rememberSecret(session2.upload_url);
@@ -2187,7 +2244,7 @@ async function putContentFile(session2, bytes, fetcher = fetch) {
2187
2244
  const result = await fetcher(url, {
2188
2245
  method: "PUT",
2189
2246
  headers: { "Content-Type": contentType, "x-amz-checksum-sha256": checksum },
2190
- body: new Uint8Array(bytes),
2247
+ body: new Uint8Array(bytes2),
2191
2248
  redirect: "error",
2192
2249
  signal: AbortSignal.timeout(12e4)
2193
2250
  });
@@ -2213,7 +2270,20 @@ function registerContentCommands(program2, runtime2) {
2213
2270
  const body = parse(createSchema, { title: opts.title, description: opts.description, kind: opts.kind, policy_version: opts.policyVersion });
2214
2271
  await mutate(ctx, "POST", "/v4/creator/contents", body);
2215
2272
  });
2216
- runtime2.action(content.command("upload <id> <file>").description("HTML/ZIP \uC5C5\uB85C\uB4DC\uB85C \uC0C8 \uBE44\uACF5\uAC1C \uBC84\uC804 \uC0DD\uC131 (creator-content:write)").addOption(new Option("--header <mode>", "\uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"])).addOption(new Option("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"])), async (ctx, [id2, file], opts) => {
2273
+ 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) => {
2274
+ const body = parse(updateSchema, {
2275
+ title: opts.title,
2276
+ description: opts.description,
2277
+ kind: opts.kind,
2278
+ thumbnail_id: opts.thumbnailId === void 0 ? void 0 : opts.thumbnailId === "none" ? null : opts.thumbnailId,
2279
+ metadata_mode: opts.metadataMode,
2280
+ metadata_lang: opts.metadataLang === void 0 ? void 0 : opts.metadataLang === "none" ? null : opts.metadataLang,
2281
+ localized_metadata: opts.localizedMetadata === void 0 ? void 0 : jsonOption("--localized-metadata", opts.localizedMetadata),
2282
+ tags: opts.tags === void 0 ? void 0 : csv(opts.tags)
2283
+ });
2284
+ await mutate(ctx, "PATCH", `/v4/creator/contents/${uuid(id2)}`, body);
2285
+ });
2286
+ 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) => {
2217
2287
  const path = `/v4/creator/contents/${uuid(id2)}`;
2218
2288
  const prepared = await prepareContentFile(file);
2219
2289
  for (const warning of prepared.warnings) process.stderr.write(`\uD328\uD0A4\uC9C0 \uACBD\uACE0: ${warning}
@@ -2233,6 +2303,13 @@ function registerContentCommands(program2, runtime2) {
2233
2303
  runtime2.action(content.command("preview <id> <version-id>").description("\uC571 \uD655\uC778\uC6A9 \uBBF8\uB9AC\uBCF4\uAE30 \uB9C1\uD06C \uC870\uD68C (creator-content:read)"), async (ctx, [id2, versionId]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/preview`)));
2234
2304
  runtime2.action(content.command("submit <id> <version-id>").description("\uC571\uC5D0\uC11C \uD655\uC778 \uC644\uB8CC\uD55C \uBC84\uC804\uC758 \uC2EC\uC0AC \uC81C\uCD9C, \uD1B5\uACFC \uC2DC \uACF5\uAC1C (creator-content:write\uC640 creator-content:publish \uB458 \uB2E4 \uD544\uC694)"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/submit`, { publish_on_approval: true }, "\uC774 \uBC84\uC804\uC744 \uC2EC\uC0AC\uD558\uACE0 \uD1B5\uACFC\uD558\uBA74 \uACF5\uAC1C\uD560\uAE4C\uC694?"));
2235
2305
  runtime2.action(content.command("withdraw <id> <version-id>").description("\uC9C4\uD589 \uC911\uC778 \uC2EC\uC0AC \uCDE8\uC18C (creator-content:write)"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/review/cancel`, void 0, "\uC774 \uBC84\uC804\uC758 \uC2EC\uC0AC\uB97C \uCDE8\uC18C\uD560\uAE4C\uC694?"));
2306
+ runtime2.action(content.command("info-version <id> <version-id>").description("\uBC14\uB010 \uCF58\uD150\uCE20 \uC815\uBCF4\uB9CC \uB2F4\uC740 \uC0C8 \uBC84\uC804 \uC0DD\uC131 (creator-content:write)"), async (ctx, [id2, versionId]) => mutate(
2307
+ ctx,
2308
+ "POST",
2309
+ `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/info-version`,
2310
+ void 0,
2311
+ "\uD604\uC7AC \uCF58\uD150\uCE20 \uC815\uBCF4\uB85C \uC774 \uBC84\uC804\uC744 \uBCF5\uC81C\uD55C \uC0C8 \uCD08\uC548\uC744 \uB9CC\uB4E4\uAE4C\uC694? \uC0C8 \uBC84\uC804\uC740 \uC571 \uD655\uC778\uACFC \uC2EC\uC0AC\uB97C \uB2E4\uC2DC \uBC1B\uC544\uC57C \uD569\uB2C8\uB2E4."
2312
+ ));
2236
2313
  runtime2.action(content.command("publish <id> <version-id>").description("\uC2B9\uC778\uB410\uC9C0\uB9CC \uACF5\uAC1C\uD558\uC9C0 \uC54A\uC740 \uBC84\uC804 \uACF5\uAC1C (creator-content:publish)"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/publish`, void 0, "\uC2B9\uC778\uB41C \uC774 \uBC84\uC804\uC744 \uACF5\uAC1C\uD560\uAE4C\uC694?"));
2237
2314
  runtime2.action(content.command("unpublish <id>").description("\uCF58\uD150\uCE20 \uAC8C\uC2DC \uC911\uB2E8 (creator-content:publish)"), async (ctx, [id2]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/unpublish`, void 0, "\uCF58\uD150\uCE20 \uAC8C\uC2DC\uB97C \uC911\uB2E8\uD560\uAE4C\uC694?"));
2238
2315
  const serverKeys = content.command("server-keys").description("\uCF58\uD150\uCE20 \uC11C\uBC84 \uD0A4 \uC870\uD68C\xB7\uD3D0\uAE30 (platform:read\xB7platform:write, \uBC1C\uAE09\xB7\uD68C\uC804\uC740 \uD06C\uB9AC\uC5D0\uC774\uD130 \uC13C\uD130)");
@@ -2271,36 +2348,161 @@ function registerContentCommands(program2, runtime2) {
2271
2348
  }
2272
2349
 
2273
2350
  // src/vendor/clack-api-v4-contracts/authoring-semantics.mjs
2274
- function inspectAuthoringSemantics(skill, form) {
2351
+ function walkDataSchema(schema, visit) {
2352
+ const defs = schema?.$defs ?? {};
2353
+ const step = (node, info) => {
2354
+ if (!node || typeof node !== "object" || Array.isArray(node)) return;
2355
+ if (typeof node.$ref === "string") {
2356
+ const key = node.$ref.startsWith("#/$defs/") ? node.$ref.slice("#/$defs/".length) : null;
2357
+ if (key === null || !Object.hasOwn(defs, key) || info.refs.includes(key)) {
2358
+ visit(node, { ...info, cycle: key !== null && info.refs.includes(key), unresolved: key === null || !Object.hasOwn(defs, key) });
2359
+ return;
2360
+ }
2361
+ visit(node, info);
2362
+ step(defs[key], { ...info, refs: [...info.refs, key] });
2363
+ return;
2364
+ }
2365
+ visit(node, info);
2366
+ for (const branch of Array.isArray(node.oneOf) ? node.oneOf : []) step(branch, { ...info, branch: true });
2367
+ for (const [name2, child] of Object.entries(node.properties && typeof node.properties === "object" ? node.properties : {})) {
2368
+ step(child, {
2369
+ ...info,
2370
+ pointer: `${info.pointer}/${name2}`,
2371
+ depth: info.depth + 1,
2372
+ root: info.depth === 0 ? name2 : info.root,
2373
+ visibility: info.depth === 0 ? child?.["x-clack-visibility"] : info.visibility,
2374
+ branch: false
2375
+ });
2376
+ }
2377
+ if (node.items && typeof node.items === "object") step(node.items, { ...info, pointer: `${info.pointer}/*`, depth: info.depth + 1, branch: false });
2378
+ };
2379
+ step(schema, { pointer: "", depth: 0, root: null, visibility: null, refs: [], branch: false });
2380
+ }
2381
+ function dataRootField(schema, name2) {
2382
+ const field2 = schema?.properties?.[name2];
2383
+ if (!field2 || typeof field2 !== "object") return null;
2384
+ if (typeof field2.$ref === "string" && field2.$ref.startsWith("#/$defs/")) {
2385
+ const target = schema.$defs?.[field2.$ref.slice("#/$defs/".length)];
2386
+ return target && typeof target === "object" ? { ...target, "x-clack-visibility": field2["x-clack-visibility"] } : null;
2387
+ }
2388
+ return field2;
2389
+ }
2390
+ function inspectEditorSemantics(skill, data, fail2) {
2391
+ const editor = skill.authoring?.editor;
2392
+ if (!editor) return;
2393
+ const permissions = new Set(editor.permissions ?? []);
2394
+ 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.");
2395
+ 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.");
2396
+ const imageTool = (skill.authoring?.tools ?? []).find((tool) => tool.tool === "image.generate");
2397
+ 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.");
2398
+ 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.");
2399
+ if (!data) return;
2400
+ let assets = 0;
2401
+ let suggest = 0;
2402
+ walkDataSchema(data, (node) => {
2403
+ if (node["x-clack-asset"]) assets++;
2404
+ if (node["x-clack-ai"]?.suggest === true) suggest++;
2405
+ });
2406
+ 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.");
2407
+ 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.");
2408
+ 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.");
2409
+ 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.");
2410
+ const metadata = skill.output?.metadata ?? {};
2411
+ for (const key of ["title", "description"]) {
2412
+ if (metadata[key] === void 0) continue;
2413
+ const field2 = dataRootField(data, metadata[key]);
2414
+ 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.");
2415
+ }
2416
+ for (const key of ["thumbnail", "thumbnail_fallback"]) {
2417
+ if (metadata[key] === void 0) continue;
2418
+ const field2 = dataRootField(data, metadata[key]);
2419
+ 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).");
2420
+ }
2421
+ }
2422
+ var INTRO_MAX_IMAGES = 8;
2423
+ var INTRO_CREATOR_COMMENT_MAX = 500;
2424
+ var INTRO_DESCRIPTION_MAX = 5e3;
2425
+ var INTRO_TEXT_WIDGETS = /* @__PURE__ */ new Set(["text", "textarea"]);
2426
+ var labelRecord = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : null;
2427
+ var introLabelText = (value2) => value2.normalize("NFC").trim();
2428
+ function introFieldLabelMaxLength(name2, field2) {
2429
+ const title = typeof field2?.title === "string" ? introLabelText(field2.title) : "";
2430
+ const lengths = [(title || name2).length];
2431
+ for (const value2 of Object.values(labelRecord(field2?.["x-clack-i18n"]) ?? {})) {
2432
+ if (typeof value2 === "string") lengths.push(introLabelText(value2).length);
2433
+ }
2434
+ return Math.max(...lengths);
2435
+ }
2436
+ function inspectIntroSemantics(skill, form, slots, fail2) {
2437
+ const intro = skill.output?.intro;
2438
+ if (!intro) return;
2439
+ let images = 0;
2440
+ for (const name2 of intro.images ?? []) {
2441
+ const slot = slots.find((entry) => entry.field === name2);
2442
+ if (!slot) {
2443
+ fail2("/output/intro/images", "\uC778\uD2B8\uB85C \uC774\uBBF8\uC9C0\uB294 output.asset_slots\uC758 \uD544\uB4DC\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
2444
+ continue;
2445
+ }
2446
+ images += slot.multiple ? slot.multiple.max : 1;
2447
+ }
2448
+ 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.`);
2449
+ if (!form) return;
2450
+ const properties = form.properties ?? {};
2451
+ const textField = (name2) => {
2452
+ const field2 = Object.hasOwn(properties, name2) ? properties[name2] : null;
2453
+ return field2 && field2.type === "string" && INTRO_TEXT_WIDGETS.has(field2["x-clack-widget"]) && Number.isSafeInteger(field2.maxLength) ? field2 : null;
2454
+ };
2455
+ for (const [key, max] of [["creator_comment", INTRO_CREATOR_COMMENT_MAX], ["description", INTRO_DESCRIPTION_MAX]]) {
2456
+ if (intro[key] === void 0) continue;
2457
+ const field2 = textField(intro[key]);
2458
+ 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.");
2459
+ 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.`);
2460
+ }
2461
+ const fallback = intro.description_fallback ?? [];
2462
+ let composed = 0;
2463
+ for (const name2 of fallback) {
2464
+ const field2 = textField(name2);
2465
+ if (!field2) {
2466
+ 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.");
2467
+ continue;
2468
+ }
2469
+ composed += introFieldLabelMaxLength(name2, field2) + 1 + field2.maxLength;
2470
+ }
2471
+ composed += Math.max(0, fallback.length - 1) * 2;
2472
+ 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.`);
2473
+ }
2474
+ function inspectAuthoringSemantics(skill, form, data) {
2275
2475
  const errors = [];
2276
- const fail = (path, message) => errors.push({ path, message });
2476
+ const fail2 = (path, message) => errors.push({ path, message });
2477
+ inspectEditorSemantics(skill, data, fail2);
2277
2478
  const tools = skill.authoring?.tools ?? [];
2278
- if (new Set(tools.map((tool) => tool.tool)).size !== tools.length) fail("/authoring/tools", "\uB3C4\uAD6C \uC774\uB984\uC744 \uC911\uBCF5 \uC120\uC5B8\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2279
- for (const tool of tools) if (tool.batch_max > tool.max_calls) fail("/authoring/tools", "\uC77C\uAD04 \uC0DD\uC131 \uC218\uB294 \uC138\uC158 \uD638\uCD9C \uC218\uB97C \uB118\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2479
+ 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.");
2480
+ 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.");
2280
2481
  const slots = skill.output?.asset_slots ?? [];
2281
2482
  const paths = /* @__PURE__ */ new Set();
2282
2483
  const fields3 = /* @__PURE__ */ new Set();
2283
2484
  for (const slot of slots) {
2284
- if (fields3.has(slot.field)) fail("/output/asset_slots", "\uC2AC\uB86F \uD544\uB4DC\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2485
+ if (fields3.has(slot.field)) fail2("/output/asset_slots", "\uC2AC\uB86F \uD544\uB4DC\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2285
2486
  fields3.add(slot.field);
2286
- if (slot.multiple && slot.multiple.min > slot.multiple.max) fail("/output/asset_slots", "\uB2E4\uC911 \uC2AC\uB86F \uCD5C\uC18C \uC218\uB294 \uCD5C\uB300 \uC218\uBCF4\uB2E4 \uD074 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2487
+ 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.");
2287
2488
  for (let index = 0; index < (slot.multiple?.max ?? 1); index++) {
2288
2489
  const path = slot.multiple ? slot.multiple.path.replace("{n}", String(index)) : slot.path;
2289
- if (paths.has(path)) fail("/output/asset_slots", "\uC2AC\uB86F \uD30C\uC77C \uACBD\uB85C\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2490
+ if (paths.has(path)) fail2("/output/asset_slots", "\uC2AC\uB86F \uD30C\uC77C \uACBD\uB85C\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2290
2491
  paths.add(path);
2291
2492
  }
2292
2493
  }
2494
+ inspectIntroSemantics(skill, form, slots, fail2);
2293
2495
  if (!form) return errors;
2294
2496
  const properties = form.properties ?? {};
2295
2497
  const exists = (expression, path) => {
2296
2498
  const match = /^([a-z][a-z0-9_-]*)(?:\[(\d+)\])?$/.exec(expression);
2297
2499
  if (!match || !Object.hasOwn(properties, match[1])) {
2298
- fail(path, "\uC120\uC5B8\uD55C \uD3FC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2500
+ fail2(path, "\uC120\uC5B8\uD55C \uD3FC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2299
2501
  return;
2300
2502
  }
2301
2503
  if (match[2] !== void 0) {
2302
2504
  const slot = slots.find((value2) => value2.field === match[1]);
2303
- if (!slot?.multiple || Number(match[2]) >= slot.multiple.max) fail(path, "\uB2E4\uC911 \uC774\uBBF8\uC9C0 \uC2AC\uB86F\uC758 \uC720\uD6A8\uD55C \uC778\uB371\uC2A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
2505
+ 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.");
2304
2506
  }
2305
2507
  };
2306
2508
  const entity = skill.output?.entity_map;
@@ -2310,30 +2512,539 @@ function inspectAuthoringSemantics(skill, form) {
2310
2512
  }
2311
2513
  for (const slot of slots) {
2312
2514
  const node = properties[slot.field];
2313
- if (!node || node["x-clack-widget"] !== (slot.multiple ? "image_list" : "image")) fail("/output/asset_slots", "\uC2AC\uB86F\uACFC \uC774\uBBF8\uC9C0 \uC704\uC82F \uC885\uB958\uAC00 \uC77C\uCE58\uD574\uC57C \uD569\uB2C8\uB2E4.");
2515
+ 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.");
2314
2516
  }
2315
2517
  for (const [name2, node] of Object.entries(properties)) {
2316
2518
  if (node["x-clack-widget"] === "content_ref") {
2317
- if (node.type !== "string" || !node["x-clack-source"]) fail(`/properties/${name2}`, "content_ref\uC5D0\uB294 \uBB38\uC790\uC5F4\uACFC x-clack-source \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
2519
+ 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.");
2318
2520
  for (const target of Object.keys(node["x-clack-source"]?.import ?? {})) {
2319
2521
  exists(target, `/properties/${name2}/x-clack-source/import`);
2320
- if (properties[target]?.["x-clack-widget"] === "content_ref") fail(`/properties/${name2}`, "\uB2E4\uB978 content_ref \uD544\uB4DC\uB85C \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2522
+ 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.");
2321
2523
  }
2322
- } else if (node["x-clack-source"]) fail(`/properties/${name2}`, "x-clack-source\uB294 content_ref\uC5D0\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4.");
2323
- if (node["x-clack-widget"] === "image_list" && (node.type !== "array" || node.items?.type !== "object" || !node["x-clack-asset"])) fail(`/properties/${name2}`, "image_list\uC5D0\uB294 \uAC1D\uCCB4 \uBC30\uC5F4\uACFC \uC790\uC0B0 \uC120\uC5B8\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
2524
+ } else if (node["x-clack-source"]) fail2(`/properties/${name2}`, "x-clack-source\uB294 content_ref\uC5D0\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4.");
2525
+ 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.");
2324
2526
  }
2325
2527
  return errors;
2326
2528
  }
2327
2529
 
2328
2530
  // src/lib/skill-package.ts
2329
- import { createHash as createHash3 } from "crypto";
2531
+ import { createHash as createHash4 } from "crypto";
2330
2532
  import { lstat as lstat4, open as open3, readdir } from "fs/promises";
2331
2533
  import { constants } from "fs";
2332
2534
  import { basename as basename4, extname as extname2, join as join2, resolve as resolve7 } from "path";
2333
- import { Ajv2020 } from "ajv/dist/2020.js";
2535
+ import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
2334
2536
  import { unzipSync as unzipSync2, zipSync } from "fflate";
2335
2537
  import { parseDocument } from "yaml";
2336
2538
 
2539
+ // src/lib/skill-editor.ts
2540
+ import { createHash as createHash3 } from "crypto";
2541
+ import { Ajv2020 } from "ajv/dist/2020.js";
2542
+
2543
+ // src/vendor/clack-api-v4-contracts/output-data-inspect.mjs
2544
+ var bytes = (value2) => Buffer.byteLength(JSON.stringify(value2), "utf8");
2545
+ var fail = (path, message) => ({ path, message });
2546
+ var OUTPUT_DATA_LIMITS = { schema_bytes: 65536, depth: 6, properties: 400, public_bytes: 262144, private_bytes: 65536, ui_state_bytes: 262144 };
2547
+ var SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "integer", "number", "boolean"]);
2548
+ var SCALAR_KEYS = {
2549
+ string: ["minLength", "maxLength", "format"],
2550
+ number: ["minimum", "maximum"],
2551
+ integer: ["minimum", "maximum"],
2552
+ array: ["items", "minItems", "maxItems", "uniqueItems"],
2553
+ object: ["properties", "required", "additionalProperties"],
2554
+ boolean: []
2555
+ };
2556
+ var TYPED_KEYS = new Set(Object.values(SCALAR_KEYS).flat());
2557
+ function inspectOutputData(schema) {
2558
+ const errors = [];
2559
+ 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."));
2560
+ 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.`));
2561
+ 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."));
2562
+ const defs = schema.$defs ?? {};
2563
+ const resolve9 = (node) => typeof node?.$ref === "string" ? defs[node.$ref.slice("#/$defs/".length)] : node;
2564
+ let properties = 0;
2565
+ const suggestRoots = [];
2566
+ const assetPointers = [];
2567
+ walkDataSchema(schema, (node, info) => {
2568
+ const at = info.pointer || "/";
2569
+ if (info.cycle) {
2570
+ errors.push(fail(at, "\uC21C\uD658 \uCC38\uC870\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."));
2571
+ return;
2572
+ }
2573
+ if (info.unresolved) {
2574
+ errors.push(fail(at, "\uB85C\uCEEC $defs \uCC38\uC870\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4."));
2575
+ return;
2576
+ }
2577
+ 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."));
2578
+ if (info.depth === 0) return;
2579
+ if (typeof node.$ref === "string") {
2580
+ 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."));
2581
+ return;
2582
+ }
2583
+ if (!info.branch) properties++;
2584
+ if (Array.isArray(node.oneOf)) {
2585
+ const discriminator = node["x-clack-discriminator"];
2586
+ if (!discriminator) errors.push(fail(at, "oneOf\uC5D0\uB294 x-clack-discriminator\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
2587
+ 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."));
2588
+ const seen = /* @__PURE__ */ new Set();
2589
+ for (const raw of node.oneOf) {
2590
+ const branch = resolve9(raw);
2591
+ const tag = branch?.properties?.[discriminator];
2592
+ if (branch?.type !== "object" || typeof tag?.const !== "string" || tag.type !== "string" || !(branch.required ?? []).includes(discriminator)) {
2593
+ 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."));
2594
+ } else if (seen.has(tag.const)) errors.push(fail(at, `\uD310\uBCC4\uC790 \uAC12 ${tag.const}\uC774 \uC911\uBCF5\uB429\uB2C8\uB2E4.`));
2595
+ else seen.add(tag.const);
2596
+ }
2597
+ return;
2598
+ }
2599
+ if (node["x-clack-discriminator"] !== void 0) errors.push(fail(at, "x-clack-discriminator\uB294 oneOf\uC5D0\uB9CC \uB461\uB2C8\uB2E4."));
2600
+ if (!node.type) {
2601
+ errors.push(fail(at, "type\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."));
2602
+ return;
2603
+ }
2604
+ 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.`));
2605
+ const range = (low, high, label) => {
2606
+ 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.`));
2607
+ };
2608
+ range("minLength", "maxLength", "\uAE38\uC774");
2609
+ range("minItems", "maxItems", "\uD56D\uBAA9 \uC218");
2610
+ range("minimum", "maximum", "\uAC12");
2611
+ if (node.type === "string" && typeof node.maxLength !== "number") errors.push(fail(at, "\uBB38\uC790\uC5F4 maxLength\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
2612
+ 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."));
2613
+ if (node.type === "array" && node.uniqueItems === true) {
2614
+ let items = node.items;
2615
+ for (let hops = 0; typeof items?.$ref === "string" && hops < 64; hops++) items = resolve9(items);
2616
+ 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."));
2617
+ }
2618
+ if (node.type === "object") {
2619
+ 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."));
2620
+ 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.`));
2621
+ }
2622
+ 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."));
2623
+ if (node["x-clack-asset"]) {
2624
+ assetPointers.push(at);
2625
+ 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."));
2626
+ if (info.visibility === "private") errors.push(fail(at, "\uBE44\uACF5\uAC1C \uD0A4\uC5D0\uB294 \uC790\uC0B0\uC744 \uB458 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
2627
+ } else if (node.format === "uuid" && node.type !== "string") errors.push(fail(at, "format\uC740 \uBB38\uC790\uC5F4\uC5D0\uB9CC \uC501\uB2C8\uB2E4."));
2628
+ if (node["x-clack-ai"]?.suggest === true) {
2629
+ if (info.visibility !== "public") errors.push(fail(at, "AI \uC81C\uC548\uC740 \uACF5\uAC1C \uD0A4\uC5D0\uB9CC \uCF24 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
2630
+ suggestRoots.push(at);
2631
+ }
2632
+ });
2633
+ 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."));
2634
+ 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."));
2635
+ return errors;
2636
+ }
2637
+ var ZWJ_AFTER = new RegExp("\\p{Extended_Pictographic}", "u");
2638
+
2639
+ // src/vendor/clack-api-v4-contracts/schemas/output-data.v1.schema.json
2640
+ var output_data_v1_schema_default = {
2641
+ $schema: "https://json-schema.org/draft/2020-12/schema",
2642
+ $id: "https://clack.kr/schemas/platform/output-data.v1.schema.json",
2643
+ $comment: "\uBAA8\uB178\uB808\uD3EC clack-api-v4/contracts/platform/schemas/output-data.v1.schema.json\uC5D0\uC11C \uBCF5\uC0AC\uD55C \uD30C\uC77C\uC774\uB2E4. \uC774 \uD30C\uC77C\uC744 \uC9C1\uC811 \uC218\uC815\uD558\uC9C0 \uB9D0\uACE0 scripts/sync-vendor-types.mjs\uB85C \uAC31\uC2E0\uD55C\uB2E4.",
2644
+ title: "\uC2A4\uD0AC \uC5D0\uB514\uD130 \uB370\uC774\uD130 \uBB38\uC11C \uC2A4\uD0A4\uB9C8 \uADDC\uCE59(output-data.v1)",
2645
+ description: "\uC2A4\uD0AC \uB9E4\uB2C8\uD398\uC2A4\uD2B8 output.data.schema\uAC00 \uAC00\uB9AC\uD0A4\uB294 \uB370\uC774\uD130 \uBB38\uC11C \uC2A4\uD0A4\uB9C8 \uD30C\uC77C\uC774 \uB530\uB77C\uC57C \uD558\uB294 \uBA54\uD0C0 \uADDC\uCE59\uC774\uB2E4. v1\uC740 pattern\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uACE0(\uBC31\uD2B8\uB798\uD0B9 \uBE44\uC6A9), uniqueItems\uB294 \uC2A4\uCE7C\uB77C \uBC30\uC5F4\uC5D0\uB9CC \uC4F4\uB2E4. \uAE4A\uC774 6\xB7\uBB38\uC790\uC5F4 maxLength \uD544\uC218\xB7\uBC30\uC5F4 maxItems \uD544\uC218\xB7\uD310\uBCC4\uC790 oneOf\xB7\uB85C\uCEEC $ref\uB9CC\xB7\uC21C\uD658 \uAE08\uC9C0\xB7\uBE44\uACF5\uAC1C\uB294 \uCD5C\uC0C1\uC704 \uD0A4\uB9CC\xB7uniqueItems \uB300\uC0C1\uCC98\uB7FC \uAC19\uC740 \uC2A4\uD0A4\uB9C8\uB85C \uD45C\uD604\uD560 \uC218 \uC5C6\uB294 \uADDC\uCE59\uC740 validators.mjs\uC758 inspectOutputData\uAC00 \uCD94\uAC00\uB85C \uAC80\uC0AC\uD55C\uB2E4.",
2646
+ type: "object",
2647
+ additionalProperties: false,
2648
+ properties: {
2649
+ $schema: {
2650
+ const: "https://json-schema.org/draft/2020-12/schema"
2651
+ },
2652
+ title: {
2653
+ type: "string",
2654
+ maxLength: 120
2655
+ },
2656
+ description: {
2657
+ type: "string",
2658
+ maxLength: 1e3
2659
+ },
2660
+ type: {
2661
+ const: "object"
2662
+ },
2663
+ additionalProperties: {
2664
+ const: false
2665
+ },
2666
+ properties: {
2667
+ type: "object",
2668
+ propertyNames: {
2669
+ $ref: "#/$defs/key"
2670
+ },
2671
+ additionalProperties: {
2672
+ $ref: "#/$defs/rootField"
2673
+ },
2674
+ minProperties: 1,
2675
+ maxProperties: 60
2676
+ },
2677
+ required: {
2678
+ $ref: "#/$defs/keyList"
2679
+ },
2680
+ $defs: {
2681
+ type: "object",
2682
+ propertyNames: {
2683
+ type: "string",
2684
+ pattern: "^[A-Za-z][A-Za-z0-9_-]{0,63}$"
2685
+ },
2686
+ additionalProperties: {
2687
+ $ref: "#/$defs/node"
2688
+ },
2689
+ maxProperties: 64
2690
+ }
2691
+ },
2692
+ required: [
2693
+ "type",
2694
+ "properties",
2695
+ "additionalProperties"
2696
+ ],
2697
+ $defs: {
2698
+ key: {
2699
+ type: "string",
2700
+ pattern: "^[a-z][a-z0-9_]{0,31}$"
2701
+ },
2702
+ keyList: {
2703
+ type: "array",
2704
+ items: {
2705
+ $ref: "#/$defs/key"
2706
+ },
2707
+ uniqueItems: true
2708
+ },
2709
+ label: {
2710
+ type: "object",
2711
+ additionalProperties: false,
2712
+ properties: {
2713
+ ko: {
2714
+ type: "string",
2715
+ minLength: 1,
2716
+ maxLength: 200
2717
+ },
2718
+ en: {
2719
+ type: "string",
2720
+ minLength: 1,
2721
+ maxLength: 200
2722
+ }
2723
+ },
2724
+ required: [
2725
+ "ko"
2726
+ ]
2727
+ },
2728
+ asset: {
2729
+ type: "object",
2730
+ additionalProperties: false,
2731
+ properties: {
2732
+ accept: {
2733
+ type: "array",
2734
+ items: {
2735
+ type: "string",
2736
+ enum: [
2737
+ "image/png",
2738
+ "image/jpeg",
2739
+ "image/webp"
2740
+ ]
2741
+ },
2742
+ minItems: 1,
2743
+ uniqueItems: true
2744
+ },
2745
+ max_bytes: {
2746
+ type: "integer",
2747
+ minimum: 1,
2748
+ maximum: 4194304
2749
+ },
2750
+ aspect: {
2751
+ type: "string",
2752
+ pattern: "^[1-9][0-9]*:[1-9][0-9]*$"
2753
+ },
2754
+ generate: {
2755
+ type: "boolean"
2756
+ }
2757
+ },
2758
+ required: [
2759
+ "accept",
2760
+ "max_bytes"
2761
+ ]
2762
+ },
2763
+ ai: {
2764
+ type: "object",
2765
+ additionalProperties: false,
2766
+ properties: {
2767
+ suggest: {
2768
+ type: "boolean"
2769
+ },
2770
+ hint: {
2771
+ type: "string",
2772
+ maxLength: 300
2773
+ }
2774
+ },
2775
+ required: [
2776
+ "suggest"
2777
+ ]
2778
+ },
2779
+ scalar: {
2780
+ type: [
2781
+ "string",
2782
+ "number",
2783
+ "boolean"
2784
+ ]
2785
+ },
2786
+ nodeKeywords: {
2787
+ type: "object",
2788
+ properties: {
2789
+ type: {
2790
+ type: "string",
2791
+ enum: [
2792
+ "object",
2793
+ "array",
2794
+ "string",
2795
+ "integer",
2796
+ "number",
2797
+ "boolean"
2798
+ ]
2799
+ },
2800
+ title: {
2801
+ type: "string",
2802
+ maxLength: 120
2803
+ },
2804
+ description: {
2805
+ type: "string",
2806
+ maxLength: 1e3
2807
+ },
2808
+ "x-clack-i18n": {
2809
+ $ref: "#/$defs/label"
2810
+ },
2811
+ enum: {
2812
+ type: "array",
2813
+ items: {
2814
+ $ref: "#/$defs/scalar"
2815
+ },
2816
+ minItems: 1,
2817
+ maxItems: 100,
2818
+ uniqueItems: true
2819
+ },
2820
+ const: {
2821
+ $ref: "#/$defs/scalar"
2822
+ },
2823
+ minLength: {
2824
+ type: "integer",
2825
+ minimum: 0
2826
+ },
2827
+ maxLength: {
2828
+ type: "integer",
2829
+ minimum: 1,
2830
+ maximum: 1e5
2831
+ },
2832
+ format: {
2833
+ const: "uuid"
2834
+ },
2835
+ minimum: {
2836
+ type: "number"
2837
+ },
2838
+ maximum: {
2839
+ type: "number"
2840
+ },
2841
+ minItems: {
2842
+ type: "integer",
2843
+ minimum: 0
2844
+ },
2845
+ maxItems: {
2846
+ type: "integer",
2847
+ minimum: 1,
2848
+ maximum: 1e3
2849
+ },
2850
+ uniqueItems: {
2851
+ type: "boolean",
2852
+ description: "\uC2A4\uCE7C\uB77C(string\xB7integer\xB7number\xB7boolean) items \uBC30\uC5F4\uC5D0\uB9CC \uC4F4\uB2E4(validators.mjs \uAC80\uC0AC)."
2853
+ },
2854
+ items: {
2855
+ $ref: "#/$defs/node"
2856
+ },
2857
+ properties: {
2858
+ type: "object",
2859
+ propertyNames: {
2860
+ $ref: "#/$defs/key"
2861
+ },
2862
+ additionalProperties: {
2863
+ $ref: "#/$defs/node"
2864
+ },
2865
+ maxProperties: 60
2866
+ },
2867
+ required: {
2868
+ $ref: "#/$defs/keyList"
2869
+ },
2870
+ additionalProperties: {
2871
+ const: false
2872
+ },
2873
+ $ref: {
2874
+ type: "string",
2875
+ pattern: "^#/\\$defs/[A-Za-z][A-Za-z0-9_-]{0,63}$"
2876
+ },
2877
+ oneOf: {
2878
+ type: "array",
2879
+ items: {
2880
+ $ref: "#/$defs/node"
2881
+ },
2882
+ minItems: 2,
2883
+ maxItems: 16
2884
+ },
2885
+ "x-clack-discriminator": {
2886
+ $ref: "#/$defs/key"
2887
+ },
2888
+ "x-clack-asset": {
2889
+ $ref: "#/$defs/asset"
2890
+ },
2891
+ "x-clack-ai": {
2892
+ $ref: "#/$defs/ai"
2893
+ }
2894
+ }
2895
+ },
2896
+ node: {
2897
+ allOf: [
2898
+ {
2899
+ $ref: "#/$defs/nodeKeywords"
2900
+ }
2901
+ ],
2902
+ unevaluatedProperties: false
2903
+ },
2904
+ rootField: {
2905
+ allOf: [
2906
+ {
2907
+ $ref: "#/$defs/nodeKeywords"
2908
+ }
2909
+ ],
2910
+ properties: {
2911
+ "x-clack-visibility": {
2912
+ type: "string",
2913
+ enum: [
2914
+ "public",
2915
+ "private"
2916
+ ]
2917
+ }
2918
+ },
2919
+ required: [
2920
+ "x-clack-visibility"
2921
+ ],
2922
+ unevaluatedProperties: false
2923
+ }
2924
+ }
2925
+ };
2926
+
2927
+ // src/lib/skill-editor.ts
2928
+ var EDITOR_BUNDLE_LIMITS = { maxFiles: 50, maxFileBytes: 512 * 1024, maxTotalBytes: 2 * 1024 * 1024, maxCodeBytes: 512 * 1024 };
2929
+ var DATA_SCHEMA_MAX_BYTES = 64 * 1024;
2930
+ var ALLOWED = /\.(?:html?|js|mjs|css|json|png|jpe?g|webp|gif|woff2?)$/i;
2931
+ var CODE = /\.(?:html?|js|mjs|css)$/i;
2932
+ var RESERVED = /^(?:runtime|form|examples|output|data)(?:\/|$)/;
2933
+ var textDecoder = new TextDecoder("utf-8", { fatal: true });
2934
+ var validateMeta = new Ajv2020({ allErrors: true, strict: false }).compile(output_data_v1_schema_default);
2935
+ var invalid = (message) => {
2936
+ throw new CliError(message, "SKILL_EDITOR_INVALID");
2937
+ };
2938
+ var isObject2 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
2939
+ var sha = (value2) => createHash3("sha256").update(value2).digest("hex");
2940
+ function selectEditorBundle(files, bundle) {
2941
+ const single = Object.hasOwn(files, bundle);
2942
+ const prefix = `${bundle}/`;
2943
+ const selected = single ? [[bundle, files[bundle]]] : Object.entries(files).filter(([path]) => path.startsWith(prefix));
2944
+ const list2 = selected.map(([path, data]) => ({ path: single ? path.split("/").at(-1) : path.slice(prefix.length), bytes: data.length, sha256: sha(data) })).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
2945
+ const computed = single ? list2[0]?.sha256 ?? "" : sha(JSON.stringify(list2));
2946
+ return { single, selected, list: list2, computed };
2947
+ }
2948
+ function computeEditorHash(manifest, files) {
2949
+ const editor = manifest.authoring?.editor;
2950
+ if (!isObject2(editor) || typeof editor.bundle !== "string") return null;
2951
+ const { list: list2, computed } = selectEditorBundle(files, editor.bundle.replace(/\/+$/, ""));
2952
+ return list2.length ? computed : null;
2953
+ }
2954
+ function parseJson(bytes2, label) {
2955
+ try {
2956
+ const value2 = JSON.parse(textDecoder.decode(bytes2));
2957
+ if (isObject2(value2)) return value2;
2958
+ } catch {
2959
+ }
2960
+ return invalid(`${label}\uC740 UTF-8 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
2961
+ }
2962
+ function scanBundle(list2, warnings) {
2963
+ for (const [path, data] of list2) {
2964
+ if (!/\.(?:html?|js|mjs|css)$/i.test(path)) continue;
2965
+ let text;
2966
+ try {
2967
+ text = textDecoder.decode(data);
2968
+ } catch {
2969
+ invalid(`${path}\uB294 UTF-8 \uD14D\uC2A4\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
2970
+ return;
2971
+ }
2972
+ if (/loading\s*=\s*["']?lazy/i.test(text)) warnings.push(`EDITOR_LAZY_LOAD: ${path}\uC5D0 loading="lazy"\uAC00 \uC788\uC2B5\uB2C8\uB2E4. \uC5D0\uB514\uD130\uB294 \uC2DC\uC791\uD560 \uB54C \uBAA8\uB4E0 \uC790\uC6D0\uC744 \uBD88\uB7EC\uC640\uC57C \uD569\uB2C8\uB2E4.`);
2973
+ if (/\bimport\s*\(/.test(text)) warnings.push(`EDITOR_LAZY_LOAD: ${path}\uC5D0 \uB3D9\uC801 import()\uAC00 \uC788\uC2B5\uB2C8\uB2E4. \uC815\uC801 import\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.`);
2974
+ if (/\.css$/i.test(path) && /url\(\s*(?!["']?data:)/i.test(text)) {
2975
+ warnings.push(`EDITOR_CSS_URL_NOT_INLINE: ${path}\uC758 CSS url()\uC740 data:\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4. \uAE00\uAF34\xB7\uBC30\uACBD \uC774\uBBF8\uC9C0\uB294 \uC778\uB77C\uC778\uD558\uC138\uC694.`);
2976
+ }
2977
+ }
2978
+ }
2979
+ function inspectEditorSkill(manifest, files, options2) {
2980
+ const authoring = manifest.authoring;
2981
+ const output2 = manifest.output;
2982
+ const editor = authoring.editor;
2983
+ const warnings = [];
2984
+ const bundle = String(editor.bundle).replace(/\/+$/, "");
2985
+ if (RESERVED.test(bundle)) invalid("editor \uBC88\uB4E4 \uACBD\uB85C\uAC00 \uC608\uC57D \uACBD\uB85C(runtime\xB7form\xB7examples\xB7output\xB7data)\uC785\uB2C8\uB2E4.");
2986
+ const { single, selected, list: list2, computed } = selectEditorBundle(files, bundle);
2987
+ if (!list2.length) invalid(`editor \uBC88\uB4E4(${bundle})\uC5D0 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`);
2988
+ const entry = single ? bundle : `${bundle}/index.html`;
2989
+ if (single ? !/\.html?$/i.test(bundle) : !list2.some((file) => file.path === "index.html")) invalid(`editor \uC9C4\uC785\uC810(${entry})\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`);
2990
+ if (list2.length > EDITOR_BUNDLE_LIMITS.maxFiles) invalid(`editor \uBC88\uB4E4 \uD30C\uC77C \uC218\uAC00 ${EDITOR_BUNDLE_LIMITS.maxFiles}\uAC1C\uB97C \uB118\uC2B5\uB2C8\uB2E4.`);
2991
+ const total = list2.reduce((sum, file) => sum + file.bytes, 0);
2992
+ if (total > EDITOR_BUNDLE_LIMITS.maxTotalBytes) invalid("editor \uBC88\uB4E4 \uC804\uCCB4 \uD06C\uAE30\uAC00 2 MiB\uB97C \uB118\uC2B5\uB2C8\uB2E4.");
2993
+ let code = 0;
2994
+ for (const [path, data2] of selected) {
2995
+ if (!ALLOWED.test(path)) invalid(`editor \uBC88\uB4E4\uC5D0 \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4: ${path}`);
2996
+ if (data2.length > EDITOR_BUNDLE_LIMITS.maxFileBytes) invalid(`editor \uBC88\uB4E4 \uD30C\uC77C\uC774 512 KiB\uB97C \uB118\uC2B5\uB2C8\uB2E4: ${path}`);
2997
+ if (CODE.test(path)) code += data2.length;
2998
+ }
2999
+ if (code > EDITOR_BUNDLE_LIMITS.maxCodeBytes) invalid("editor \uBC88\uB4E4 \uCF54\uB4DC(HTML\xB7JS\xB7CSS) \uD569\uACC4\uAC00 512 KiB\uB97C \uB118\uC2B5\uB2C8\uB2E4.");
3000
+ scanBundle(selected, warnings);
3001
+ const declared = String(editor.sha256);
3002
+ const match = declared === computed;
3003
+ if (!match) {
3004
+ const message = `authoring.editor.sha256\uC774 \uBC88\uB4E4 \uD574\uC2DC\uC640 \uB2E4\uB985\uB2C8\uB2E4(\uACC4\uC0B0\uAC12 ${computed}). \`clack skill pack\`\uC774 \uAC12\uC744 \uCC44\uC6C1\uB2C8\uB2E4.`;
3005
+ if (options2.hashMismatch === "error") invalid(message);
3006
+ warnings.push(`PLUGIN_BUNDLE_HASH_MISMATCH: ${message}`);
3007
+ }
3008
+ const data = output2.data;
3009
+ const schemaPath = data?.schema;
3010
+ if (typeof schemaPath !== "string" || !files[schemaPath]) return invalid("output.data.schema \uD30C\uC77C\uC774 \uD328\uD0A4\uC9C0\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4.");
3011
+ if (files[schemaPath].length > DATA_SCHEMA_MAX_BYTES) invalid("\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8 \uD30C\uC77C\uC740 64 KiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
3012
+ const schema = parseJson(files[schemaPath], "\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8");
3013
+ if (!validateMeta(schema)) {
3014
+ invalid(`\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8\uAC00 output-data.v1\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${(validateMeta.errors ?? []).slice(0, 5).map((error2) => `${error2.instancePath || "/"} ${error2.message ?? ""}`).join(" / ")}`);
3015
+ }
3016
+ const dataErrors = [...inspectOutputData(schema), ...inspectAuthoringSemantics(manifest, void 0, schema)];
3017
+ if (dataErrors.length) invalid(dataErrors.map((error2) => `${error2.path}: ${error2.message}`).join(" / "));
3018
+ const template = output2.template;
3019
+ if (!template || template.root !== "runtime" || typeof template.entry !== "string") invalid("runtime \uD15C\uD50C\uB9BF \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
3020
+ if (Object.keys(files).some((path) => path.startsWith("runtime/data/"))) invalid("runtime/data/ \uACBD\uB85C\uB294 \uC5D0\uB514\uD130 \uC2A4\uD0AC\uC5D0\uC11C \uC608\uC57D\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4(data/document.json\uACFC \uCDA9\uB3CC).");
3021
+ const examples = Object.keys(files).filter((path) => /^examples\/[^/]+\.json$/.test(path));
3022
+ if (!examples.length || examples.length > 10) invalid("\uD15C\uD50C\uB9BF \uC608\uC2DC\uB294 1~10\uAC1C \uD544\uC694\uD569\uB2C8\uB2E4.");
3023
+ let validateExample;
3024
+ try {
3025
+ const ajv2 = new Ajv2020({ allErrors: true, strict: false });
3026
+ ajv2.addFormat("uuid", /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
3027
+ validateExample = ajv2.compile(schema);
3028
+ } catch {
3029
+ return invalid("\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8\uB97C \uCEF4\uD30C\uC77C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
3030
+ }
3031
+ for (const path of examples) {
3032
+ if (files[path].length > 256 * 1024) invalid(`\uC608\uC2DC\uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4: ${path}`);
3033
+ if (!validateExample(parseJson(files[path], "\uC608\uC2DC"))) invalid(`\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8\uC640 \uC608\uC2DC\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${path}`);
3034
+ }
3035
+ return { computedHash: computed, warnings, summary: {
3036
+ bundle,
3037
+ entry,
3038
+ file_count: list2.length,
3039
+ total_bytes: total,
3040
+ declared_sha256: declared,
3041
+ computed_sha256: computed,
3042
+ hash_match: match,
3043
+ permissions: editor.permissions,
3044
+ center_only: true
3045
+ } };
3046
+ }
3047
+
2337
3048
  // src/vendor/clack-api-v4-contracts/schemas/clack-skill.v1.schema.json
2338
3049
  var clack_skill_v1_schema_default = {
2339
3050
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -2435,13 +3146,74 @@ var clack_skill_v1_schema_default = {
2435
3146
  },
2436
3147
  maxItems: 10,
2437
3148
  uniqueItems: true
3149
+ },
3150
+ description: {
3151
+ type: "object",
3152
+ properties: {
3153
+ ko: {
3154
+ type: "string",
3155
+ minLength: 1,
3156
+ maxLength: 1e3
3157
+ },
3158
+ en: {
3159
+ type: "string",
3160
+ minLength: 1,
3161
+ maxLength: 1e3
3162
+ }
3163
+ },
3164
+ required: [
3165
+ "ko"
3166
+ ],
3167
+ additionalProperties: false
3168
+ },
3169
+ tags_i18n: {
3170
+ type: "object",
3171
+ properties: {
3172
+ en: {
3173
+ type: "array",
3174
+ items: {
3175
+ type: "string",
3176
+ minLength: 1,
3177
+ maxLength: 40
3178
+ },
3179
+ minItems: 1,
3180
+ maxItems: 10,
3181
+ uniqueItems: true
3182
+ }
3183
+ },
3184
+ minProperties: 1,
3185
+ additionalProperties: false
3186
+ },
3187
+ release_notes: {
3188
+ type: "object",
3189
+ properties: {
3190
+ ko: {
3191
+ type: "string",
3192
+ minLength: 1,
3193
+ maxLength: 1e3
3194
+ },
3195
+ en: {
3196
+ type: "string",
3197
+ minLength: 1,
3198
+ maxLength: 1e3
3199
+ }
3200
+ },
3201
+ required: [
3202
+ "ko"
3203
+ ],
3204
+ additionalProperties: false
2438
3205
  }
2439
3206
  },
2440
3207
  required: [
2441
3208
  "title",
2442
3209
  "summary",
2443
3210
  "category"
2444
- ]
3211
+ ],
3212
+ dependentRequired: {
3213
+ tags_i18n: [
3214
+ "tags"
3215
+ ]
3216
+ }
2445
3217
  },
2446
3218
  authoring: {
2447
3219
  type: "object",
@@ -2624,6 +3396,64 @@ var clack_skill_v1_schema_default = {
2624
3396
  "permissions",
2625
3397
  "purpose"
2626
3398
  ]
3399
+ },
3400
+ editor: {
3401
+ type: "object",
3402
+ additionalProperties: false,
3403
+ description: "\uC2A4\uD0AC \uC5D0\uB514\uD130(\uC804\uCCB4 \uD654\uBA74 \uC81C\uC791 UI) \uC120\uC5B8. v1\uC740 template \uC2A4\uD0AC \uC804\uC6A9\uC774\uBA70 \uC81C\uC791 \uD3FC\xB7\uD50C\uB7EC\uADF8\uC778 \uD328\uB110\uACFC \uD568\uAED8 \uC4F8 \uC218 \uC5C6\uB2E4. \uBC88\uB4E4 \uADDC\uCE59(\uB514\uB809\uD130\uB9AC\uBA74 {bundle}/index.html \uC9C4\uC785, sha256 \uACC4\uC0B0\uBC95)\uC740 authoring.plugin\uACFC \uAC19\uB2E4.",
3404
+ properties: {
3405
+ bundle: {
3406
+ type: "string",
3407
+ pattern: "^(?!/)(?!.*\\.\\.)[A-Za-z0-9_./-]+$"
3408
+ },
3409
+ sha256: {
3410
+ type: "string",
3411
+ pattern: "^[a-f0-9]{64}$"
3412
+ },
3413
+ permissions: {
3414
+ type: "array",
3415
+ items: {
3416
+ type: "string",
3417
+ enum: [
3418
+ "document.read",
3419
+ "document.write",
3420
+ "assets.read",
3421
+ "assets.pick",
3422
+ "ai.suggest",
3423
+ "tools.image.generate",
3424
+ "preview",
3425
+ "package"
3426
+ ]
3427
+ },
3428
+ minItems: 2,
3429
+ uniqueItems: true
3430
+ },
3431
+ purpose: {
3432
+ type: "object",
3433
+ additionalProperties: false,
3434
+ properties: {
3435
+ ko: {
3436
+ type: "string",
3437
+ minLength: 1,
3438
+ maxLength: 200
3439
+ },
3440
+ en: {
3441
+ type: "string",
3442
+ minLength: 1,
3443
+ maxLength: 200
3444
+ }
3445
+ },
3446
+ required: [
3447
+ "ko"
3448
+ ]
3449
+ }
3450
+ },
3451
+ required: [
3452
+ "bundle",
3453
+ "sha256",
3454
+ "permissions",
3455
+ "purpose"
3456
+ ]
2627
3457
  }
2628
3458
  },
2629
3459
  required: [
@@ -2885,21 +3715,105 @@ var clack_skill_v1_schema_default = {
2885
3715
  "fixed",
2886
3716
  "scroll_hide",
2887
3717
  "translucent_scroll_hide",
2888
- "floating_close"
3718
+ "floating_close",
3719
+ "floating_close_safe_area"
2889
3720
  ]
2890
3721
  },
2891
- color_scheme: {
2892
- enum: [
2893
- "light",
2894
- "dark"
2895
- ]
3722
+ color_scheme: {
3723
+ enum: [
3724
+ "light",
3725
+ "dark"
3726
+ ]
3727
+ }
3728
+ },
3729
+ required: [
3730
+ "schema_version",
3731
+ "header_mode",
3732
+ "color_scheme"
3733
+ ]
3734
+ },
3735
+ data: {
3736
+ type: "object",
3737
+ additionalProperties: false,
3738
+ description: "\uC2A4\uD0AC \uC5D0\uB514\uD130\uAC00 \uB9CC\uB4DC\uB294 \uB370\uC774\uD130 \uBB38\uC11C \uC120\uC5B8. schema \uD30C\uC77C\uC740 output-data.v1 \uADDC\uCE59\uC744 \uB530\uB978\uB2E4. \uBC88\uB4E4\uC5D0\uB294 \uACF5\uAC1C \uBD80\uBD84\uC774 data/document.json, \uBE44\uACF5\uAC1C \uCD5C\uC0C1\uC704 \uD0A4\uAC00 .clack/config.private.json, \uC790\uC0B0\uC774 data/assets/\uB85C \uB4E4\uC5B4\uAC04\uB2E4.",
3739
+ properties: {
3740
+ schema: {
3741
+ type: "string",
3742
+ pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[A-Za-z0-9_./-]+$"
3743
+ },
3744
+ max_bytes: {
3745
+ type: "integer",
3746
+ minimum: 1024,
3747
+ maximum: 262144
3748
+ },
3749
+ private_max_bytes: {
3750
+ type: "integer",
3751
+ minimum: 1,
3752
+ maximum: 65536
3753
+ },
3754
+ assets: {
3755
+ type: "object",
3756
+ additionalProperties: false,
3757
+ properties: {
3758
+ max_count: {
3759
+ type: "integer",
3760
+ minimum: 1,
3761
+ maximum: 48
3762
+ },
3763
+ max_total_bytes: {
3764
+ type: "integer",
3765
+ minimum: 1,
3766
+ maximum: 25165824
3767
+ }
3768
+ },
3769
+ required: [
3770
+ "max_count",
3771
+ "max_total_bytes"
3772
+ ]
3773
+ }
3774
+ },
3775
+ required: [
3776
+ "schema"
3777
+ ]
3778
+ },
3779
+ intro: {
3780
+ type: "object",
3781
+ additionalProperties: false,
3782
+ description: "\uCF58\uD150\uCE20 \uC778\uD2B8\uB85C(\uC2E4\uD589 \uC804 \uAE30\uBCF8 \uC815\uBCF4 \uD654\uBA74) \uCD9C\uB825 \uC120\uC5B8. \uBAA8\uB4E0 \uD0A4\uB294 \uC120\uD0DD\uC774\uB2E4. creator_comment\xB7description\uC740 \uACF5\uAC1C \uBB38\uC790\uC5F4 \uD3FC \uD544\uB4DC(\uCD5C\uB300 \uAE38\uC774 500\xB75000 \uC774\uD558), description_fallback\uC740 description \uAC12\uC774 \uBE44\uC5C8\uC744 \uB54C \uC21C\uC11C\uB300\uB85C \uAC12\uC774 \uC788\uB294 \uD544\uB4DC\uB9CC '{\uD3FC \uD544\uB4DC title}\\n{\uAC12}'\uC744 \uBE48 \uC904\uB85C \uC774\uC5B4 \uC0C1\uC138 \uC124\uBA85\uC744 \uC790\uB3D9 \uAD6C\uC131\uD560 \uBB38\uC790\uC5F4 \uD3FC \uD544\uB4DC\uB2E4(\uBE44\uACF5\uAC1C \uD544\uB4DC\uB97C \uACF5\uAC1C\uD558\uB294 \uC720\uC77C\uD55C \uBA85\uC2DC \uC608\uC678, \uCD5C\uB300 \uAE38\uC774 \uD569\uACFC \uB77C\uBCA8\uC774 5000\uC790 \uC774\uD558). images\uB294 output.asset_slots\uC758 field\uC774\uBA70 \uC2AC\uB86F \uC21C\uC11C\uB300\uB85C \uD3BC\uCE5C \uC81C\uC791 \uC790\uC0B0\uC744 \uC4F4\uB2E4(\uB2E8\uC77C 1\uC7A5\xB7multiple\uC740 max\uC7A5\uC73C\uB85C \uC138\uC5B4 \uD569\uACC4 8 \uC774\uD558). \uD3FC \uC2A4\uD0AC \uC804\uC6A9\uC774\uBA70 \uC5D0\uB514\uD130 \uC2A4\uD0AC\xB7\uC9C0\uCE68\uD615(instruction) \uC2A4\uD0AC\uC740 \uC4F8 \uC218 \uC5C6\uB2E4.",
3783
+ properties: {
3784
+ default_enabled: {
3785
+ type: "boolean",
3786
+ default: false
3787
+ },
3788
+ creator_comment: {
3789
+ type: "string",
3790
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
3791
+ },
3792
+ description: {
3793
+ type: "string",
3794
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
3795
+ },
3796
+ description_fallback: {
3797
+ type: "array",
3798
+ items: {
3799
+ type: "string",
3800
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
3801
+ },
3802
+ minItems: 1,
3803
+ maxItems: 20,
3804
+ uniqueItems: true
3805
+ },
3806
+ images: {
3807
+ type: "array",
3808
+ items: {
3809
+ type: "string",
3810
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
3811
+ },
3812
+ minItems: 1,
3813
+ maxItems: 8,
3814
+ uniqueItems: true
2896
3815
  }
2897
- },
2898
- required: [
2899
- "schema_version",
2900
- "header_mode",
2901
- "color_scheme"
2902
- ]
3816
+ }
2903
3817
  }
2904
3818
  },
2905
3819
  required: [
@@ -3042,8 +3956,27 @@ var clack_skill_v1_schema_default = {
3042
3956
  ],
3043
3957
  properties: {
3044
3958
  authoring: {
3045
- required: [
3046
- "form"
3959
+ oneOf: [
3960
+ {
3961
+ required: [
3962
+ "form"
3963
+ ],
3964
+ not: {
3965
+ required: [
3966
+ "editor"
3967
+ ]
3968
+ }
3969
+ },
3970
+ {
3971
+ required: [
3972
+ "editor"
3973
+ ],
3974
+ not: {
3975
+ required: [
3976
+ "form"
3977
+ ]
3978
+ }
3979
+ }
3047
3980
  ],
3048
3981
  properties: {
3049
3982
  modes: {
@@ -3094,6 +4027,136 @@ var clack_skill_v1_schema_default = {
3094
4027
  "runtime"
3095
4028
  ]
3096
4029
  }
4030
+ },
4031
+ {
4032
+ if: {
4033
+ properties: {
4034
+ authoring: {
4035
+ required: [
4036
+ "editor"
4037
+ ]
4038
+ }
4039
+ },
4040
+ required: [
4041
+ "authoring"
4042
+ ]
4043
+ },
4044
+ then: {
4045
+ properties: {
4046
+ type: {
4047
+ const: "template"
4048
+ },
4049
+ authoring: {
4050
+ properties: {
4051
+ surfaces: {
4052
+ items: {
4053
+ const: "center"
4054
+ },
4055
+ maxItems: 1
4056
+ }
4057
+ },
4058
+ not: {
4059
+ anyOf: [
4060
+ {
4061
+ required: [
4062
+ "form"
4063
+ ]
4064
+ },
4065
+ {
4066
+ required: [
4067
+ "plugin"
4068
+ ]
4069
+ }
4070
+ ]
4071
+ }
4072
+ },
4073
+ output: {
4074
+ required: [
4075
+ "data"
4076
+ ],
4077
+ not: {
4078
+ anyOf: [
4079
+ {
4080
+ required: [
4081
+ "asset_slots"
4082
+ ]
4083
+ },
4084
+ {
4085
+ required: [
4086
+ "derive"
4087
+ ]
4088
+ },
4089
+ {
4090
+ required: [
4091
+ "profile_select"
4092
+ ]
4093
+ },
4094
+ {
4095
+ required: [
4096
+ "entity"
4097
+ ]
4098
+ },
4099
+ {
4100
+ required: [
4101
+ "entity_map"
4102
+ ]
4103
+ },
4104
+ {
4105
+ required: [
4106
+ "intro"
4107
+ ]
4108
+ }
4109
+ ]
4110
+ }
4111
+ }
4112
+ }
4113
+ }
4114
+ },
4115
+ {
4116
+ if: {
4117
+ properties: {
4118
+ output: {
4119
+ required: [
4120
+ "data"
4121
+ ]
4122
+ }
4123
+ },
4124
+ required: [
4125
+ "output"
4126
+ ]
4127
+ },
4128
+ then: {
4129
+ properties: {
4130
+ authoring: {
4131
+ required: [
4132
+ "editor"
4133
+ ]
4134
+ }
4135
+ }
4136
+ }
4137
+ },
4138
+ {
4139
+ if: {
4140
+ properties: {
4141
+ type: {
4142
+ const: "instruction"
4143
+ }
4144
+ },
4145
+ required: [
4146
+ "type"
4147
+ ]
4148
+ },
4149
+ then: {
4150
+ properties: {
4151
+ output: {
4152
+ not: {
4153
+ required: [
4154
+ "intro"
4155
+ ]
4156
+ }
4157
+ }
4158
+ }
4159
+ }
3097
4160
  }
3098
4161
  ]
3099
4162
  };
@@ -3312,6 +4375,44 @@ var authoring_form_v1_schema_default = {
3312
4375
  ],
3313
4376
  additionalProperties: false
3314
4377
  },
4378
+ "x-clack-help-i18n": {
4379
+ type: "object",
4380
+ properties: {
4381
+ ko: {
4382
+ type: "string",
4383
+ minLength: 1,
4384
+ maxLength: 1e3
4385
+ },
4386
+ en: {
4387
+ type: "string",
4388
+ minLength: 1,
4389
+ maxLength: 1e3
4390
+ }
4391
+ },
4392
+ required: [
4393
+ "ko"
4394
+ ],
4395
+ additionalProperties: false
4396
+ },
4397
+ "x-clack-placeholder-i18n": {
4398
+ type: "object",
4399
+ properties: {
4400
+ ko: {
4401
+ type: "string",
4402
+ minLength: 1,
4403
+ maxLength: 300
4404
+ },
4405
+ en: {
4406
+ type: "string",
4407
+ minLength: 1,
4408
+ maxLength: 300
4409
+ }
4410
+ },
4411
+ required: [
4412
+ "ko"
4413
+ ],
4414
+ additionalProperties: false
4415
+ },
3315
4416
  "x-clack-sections": {
3316
4417
  type: "array",
3317
4418
  items: {
@@ -3583,6 +4684,44 @@ var authoring_form_v1_schema_default = {
3583
4684
  ],
3584
4685
  additionalProperties: false
3585
4686
  },
4687
+ "x-clack-help-i18n": {
4688
+ type: "object",
4689
+ properties: {
4690
+ ko: {
4691
+ type: "string",
4692
+ minLength: 1,
4693
+ maxLength: 1e3
4694
+ },
4695
+ en: {
4696
+ type: "string",
4697
+ minLength: 1,
4698
+ maxLength: 1e3
4699
+ }
4700
+ },
4701
+ required: [
4702
+ "ko"
4703
+ ],
4704
+ additionalProperties: false
4705
+ },
4706
+ "x-clack-placeholder-i18n": {
4707
+ type: "object",
4708
+ properties: {
4709
+ ko: {
4710
+ type: "string",
4711
+ minLength: 1,
4712
+ maxLength: 300
4713
+ },
4714
+ en: {
4715
+ type: "string",
4716
+ minLength: 1,
4717
+ maxLength: 300
4718
+ }
4719
+ },
4720
+ required: [
4721
+ "ko"
4722
+ ],
4723
+ additionalProperties: false
4724
+ },
3586
4725
  "x-clack-source": {
3587
4726
  type: "object",
3588
4727
  additionalProperties: false,
@@ -3977,39 +5116,39 @@ var clack_content_v1_schema_default = {
3977
5116
  var MAX_ZIP = 10 * 1024 * 1024;
3978
5117
  var MAX_EXPANDED = 30 * 1024 * 1024;
3979
5118
  var MAX_FILES2 = 500;
3980
- var textDecoder = new TextDecoder("utf-8", { fatal: true });
3981
- var ajv = new Ajv2020({ allErrors: true, strict: false });
5119
+ var textDecoder2 = new TextDecoder("utf-8", { fatal: true });
5120
+ var ajv = new Ajv20202({ allErrors: true, strict: false });
3982
5121
  ajv.addSchema(clack_content_v1_schema_default);
3983
5122
  var validateManifest = ajv.compile(clack_skill_v1_schema_default);
3984
- var validateForm = new Ajv2020({ allErrors: true, strict: false }).compile(authoring_form_v1_schema_default);
3985
- function invalid(message) {
5123
+ var validateForm = new Ajv20202({ allErrors: true, strict: false }).compile(authoring_form_v1_schema_default);
5124
+ function invalid2(message) {
3986
5125
  throw new CliError(message, "SKILL_PACKAGE_INVALID");
3987
5126
  }
3988
- function hash(bytes) {
3989
- return createHash3("sha256").update(bytes).digest("hex");
5127
+ function hash(bytes2) {
5128
+ return createHash4("sha256").update(bytes2).digest("hex");
3990
5129
  }
3991
5130
  function object(value2) {
3992
5131
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
3993
5132
  }
3994
- function json(bytes, max, label) {
3995
- if (bytes.length > max) invalid(`${label} \uD06C\uAE30 \uC81C\uD55C\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.`);
5133
+ function json(bytes2, max, label) {
5134
+ if (bytes2.length > max) invalid2(`${label} \uD06C\uAE30 \uC81C\uD55C\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.`);
3996
5135
  try {
3997
- const parsed = JSON.parse(textDecoder.decode(bytes));
5136
+ const parsed = JSON.parse(textDecoder2.decode(bytes2));
3998
5137
  if (object(parsed)) return parsed;
3999
5138
  } catch {
4000
5139
  }
4001
- return invalid(`${label}\uC740 UTF-8 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
5140
+ return invalid2(`${label}\uC740 UTF-8 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
4002
5141
  }
4003
5142
  async function readRegular(path) {
4004
5143
  let handle;
4005
5144
  try {
4006
5145
  handle = await open3(path, constants.O_RDONLY | constants.O_NOFOLLOW);
4007
5146
  const info = await handle.stat();
4008
- if (!info.isFile()) invalid("\uC2A4\uD0AC \uD328\uD0A4\uC9C0\uC5D0\uB294 \uC77C\uBC18 \uD30C\uC77C\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.");
5147
+ if (!info.isFile()) invalid2("\uC2A4\uD0AC \uD328\uD0A4\uC9C0\uC5D0\uB294 \uC77C\uBC18 \uD30C\uC77C\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.");
4009
5148
  return await handle.readFile();
4010
5149
  } catch (error2) {
4011
5150
  if (error2 instanceof CliError) throw error2;
4012
- return invalid("\uC2A4\uD0AC \uD30C\uC77C\uC744 \uC548\uC804\uD558\uAC8C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5151
+ return invalid2("\uC2A4\uD0AC \uD30C\uC77C\uC744 \uC548\uC804\uD558\uAC8C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4013
5152
  } finally {
4014
5153
  await handle?.close();
4015
5154
  }
@@ -4028,76 +5167,76 @@ async function collectFiles(directory) {
4028
5167
  const entries = await readdir(join2(directory, relative), { withFileTypes: true });
4029
5168
  for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
4030
5169
  const path = relative ? `${relative}/${entry.name}` : entry.name;
4031
- if (!skillPathAllowed(path)) invalid(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD328\uD0A4\uC9C0 \uACBD\uB85C: ${path}`);
4032
- if (entry.isSymbolicLink() || !(entry.isDirectory() || entry.isFile())) invalid(`\uC77C\uBC18 \uD30C\uC77C\xB7\uD3F4\uB354\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4: ${path}`);
5170
+ if (!skillPathAllowed(path)) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD328\uD0A4\uC9C0 \uACBD\uB85C: ${path}`);
5171
+ if (entry.isSymbolicLink() || !(entry.isDirectory() || entry.isFile())) invalid2(`\uC77C\uBC18 \uD30C\uC77C\xB7\uD3F4\uB354\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4: ${path}`);
4033
5172
  if (entry.isDirectory()) {
4034
5173
  await walk(path);
4035
5174
  continue;
4036
5175
  }
4037
- if (Object.keys(files).length >= MAX_FILES2) invalid("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
5176
+ if (Object.keys(files).length >= MAX_FILES2) invalid2("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
4038
5177
  const data = await readRegular(join2(directory, path));
4039
5178
  totalBytes += data.length;
4040
- if (totalBytes > MAX_EXPANDED) invalid("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
5179
+ if (totalBytes > MAX_EXPANDED) invalid2("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
4041
5180
  files[path] = data;
4042
5181
  }
4043
5182
  }
4044
5183
  await walk("");
4045
5184
  return files;
4046
5185
  }
4047
- function inspectZip(bytes, expectedDirectory) {
4048
- if (!bytes.length || bytes.length > MAX_ZIP) invalid("\uC2A4\uD0AC ZIP\uC740 \uBE44\uC5B4 \uC788\uC9C0 \uC54A\uC740 10 MiB \uC774\uD558 \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
5186
+ function inspectZip(bytes2, expectedDirectory, hashMismatch) {
5187
+ 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.");
4049
5188
  const seen = /* @__PURE__ */ new Set();
4050
5189
  let entries = 0;
4051
5190
  let totalBytes = 0;
4052
5191
  let extracted;
4053
5192
  try {
4054
- extracted = unzipSync2(bytes, { filter(entry) {
5193
+ extracted = unzipSync2(bytes2, { filter(entry) {
4055
5194
  entries++;
4056
- if (entries > MAX_FILES2) invalid("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
5195
+ if (entries > MAX_FILES2) invalid2("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
4057
5196
  const path = entry.name.endsWith("/") ? entry.name.slice(0, -1) : entry.name;
4058
- if (!skillPathAllowed(path) || seen.has(path.toLowerCase())) invalid(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${path}`);
5197
+ if (!skillPathAllowed(path) || seen.has(path.toLowerCase())) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${path}`);
4059
5198
  seen.add(path.toLowerCase());
4060
5199
  if (entry.name.endsWith("/")) return false;
4061
5200
  totalBytes += entry.originalSize;
4062
- if (totalBytes > MAX_EXPANDED) invalid("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
5201
+ if (totalBytes > MAX_EXPANDED) invalid2("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
4063
5202
  return true;
4064
5203
  } });
4065
5204
  } catch (error2) {
4066
5205
  if (error2 instanceof CliError) throw error2;
4067
- return invalid("ZIP \uAD6C\uC870\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5206
+ return invalid2("ZIP \uAD6C\uC870\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4068
5207
  }
4069
5208
  const markdown = extracted["SKILL.md"];
4070
5209
  const manifestBytes = extracted["clack.skill.json"];
4071
- if (!markdown || !manifestBytes) invalid("\uB8E8\uD2B8 SKILL.md\uC640 clack.skill.json\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
4072
- if (markdown.length > 20 * 1024) invalid("SKILL.md\uB294 20 KiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
5210
+ if (!markdown || !manifestBytes) invalid2("\uB8E8\uD2B8 SKILL.md\uC640 clack.skill.json\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
5211
+ if (markdown.length > 20 * 1024) invalid2("SKILL.md\uB294 20 KiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
4073
5212
  let frontmatter;
4074
5213
  try {
4075
- const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(textDecoder.decode(markdown));
4076
- if (!match) invalid("SKILL.md\uC758 YAML frontmatter\uB97C \uD655\uC778\uD558\uC138\uC694.");
5214
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(textDecoder2.decode(markdown));
5215
+ if (!match) invalid2("SKILL.md\uC758 YAML frontmatter\uB97C \uD655\uC778\uD558\uC138\uC694.");
4077
5216
  frontmatter = match[1];
4078
5217
  } catch {
4079
- return invalid("SKILL.md\uB294 UTF-8 \uD14D\uC2A4\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
5218
+ return invalid2("SKILL.md\uB294 UTF-8 \uD14D\uC2A4\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
4080
5219
  }
4081
5220
  const document = parseDocument(frontmatter, { version: "1.2", uniqueKeys: true, merge: false });
4082
5221
  if (document.errors.length || document.warnings.length || /(?:^|\n)\s*allowed-tools\s*:/i.test(frontmatter)) {
4083
- invalid("SKILL.md frontmatter\uC5D0 \uC798\uBABB\uB41C \uAC12 \uB610\uB294 allowed-tools\uAC00 \uC788\uC2B5\uB2C8\uB2E4.");
5222
+ invalid2("SKILL.md frontmatter\uC5D0 \uC798\uBABB\uB41C \uAC12 \uB610\uB294 allowed-tools\uAC00 \uC788\uC2B5\uB2C8\uB2E4.");
4084
5223
  }
4085
5224
  let front;
4086
5225
  try {
4087
5226
  front = document.toJS({ maxAliasCount: 0 });
4088
5227
  } catch {
4089
- return invalid("SKILL.md frontmatter\uB97C \uD655\uC778\uD558\uC138\uC694.");
5228
+ return invalid2("SKILL.md frontmatter\uB97C \uD655\uC778\uD558\uC138\uC694.");
4090
5229
  }
4091
- if (!object(front) || typeof front.name !== "string" || typeof front.description !== "string" || !front.description.length || front.description.length > 1024) invalid("SKILL.md\uC758 name\xB7description\uC744 \uD655\uC778\uD558\uC138\uC694.");
5230
+ if (!object(front) || typeof front.name !== "string" || typeof front.description !== "string" || !front.description.length || front.description.length > 1024) invalid2("SKILL.md\uC758 name\xB7description\uC744 \uD655\uC778\uD558\uC138\uC694.");
4092
5231
  const manifest = json(manifestBytes, 64 * 1024, "clack.skill.json");
4093
- if (!validateManifest(manifest)) invalid("clack.skill.json\uC774 \uACF5\uAC1C \uC2A4\uD0AC \uC2A4\uD0A4\uB9C8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
5232
+ if (!validateManifest(manifest)) invalid2("clack.skill.json\uC774 \uACF5\uAC1C \uC2A4\uD0AC \uC2A4\uD0A4\uB9C8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
4094
5233
  if (manifest.name !== front.name || expectedDirectory && manifest.name !== expectedDirectory && manifest.version !== expectedDirectory || object(front.metadata) && front.metadata.version !== void 0 && front.metadata.version !== manifest.version) {
4095
- invalid("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC\xB7SKILL.md\xB7\uB9E4\uB2C8\uD398\uC2A4\uD2B8\uC758 \uC774\uB984 \uB610\uB294 \uBC84\uC804\uC774 \uB2E4\uB985\uB2C8\uB2E4.");
5234
+ invalid2("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC\xB7SKILL.md\xB7\uB9E4\uB2C8\uD398\uC2A4\uD2B8\uC758 \uC774\uB984 \uB610\uB294 \uBC84\uC804\uC774 \uB2E4\uB985\uB2C8\uB2E4.");
4096
5235
  }
4097
5236
  const file = (path, max) => {
4098
- if (typeof path !== "string" || !skillPathAllowed(path) || !extracted[path]) invalid("\uB9E4\uB2C8\uD398\uC2A4\uD2B8\uC5D0 \uC120\uC5B8\uD55C \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
5237
+ if (typeof path !== "string" || !skillPathAllowed(path) || !extracted[path]) invalid2("\uB9E4\uB2C8\uD398\uC2A4\uD2B8\uC5D0 \uC120\uC5B8\uD55C \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
4099
5238
  const data = extracted[path];
4100
- if (max && data.length > max) invalid("\uC120\uC5B8\uD55C \uD30C\uC77C\uC758 \uD06C\uAE30 \uC81C\uD55C\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
5239
+ if (max && data.length > max) invalid2("\uC120\uC5B8\uD55C \uD30C\uC77C\uC758 \uD06C\uAE30 \uC81C\uD55C\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
4101
5240
  return data;
4102
5241
  };
4103
5242
  const display = manifest.display;
@@ -4106,50 +5245,93 @@ function inspectZip(bytes, expectedDirectory) {
4106
5245
  const authoring = manifest.authoring;
4107
5246
  const output2 = manifest.output;
4108
5247
  for (const reference of authoring.references ?? []) file(reference, 64 * 1024);
4109
- if (manifest.type === "template" || manifest.type === "tool") {
5248
+ let editor;
5249
+ if (manifest.type === "template" && authoring.editor) {
5250
+ editor = inspectEditorSkill(manifest, extracted, { hashMismatch });
5251
+ file(`runtime/${output2.template.entry}`);
5252
+ const runtime2 = manifest.runtime;
5253
+ for (const profile of runtime2.profiles) file(`runtime/.clack/profiles/${profile}.yaml`, 16 * 1024);
5254
+ } else if (manifest.type === "template" || manifest.type === "tool") {
4110
5255
  const form = json(file(authoring.form, 64 * 1024), 64 * 1024, "\uC81C\uC791 \uD3FC");
4111
- if (!validateForm(form)) invalid("\uC81C\uC791 \uD3FC\uC774 \uACF5\uAC1C \uC2A4\uD0A4\uB9C8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
5256
+ if (!validateForm(form)) invalid2("\uC81C\uC791 \uD3FC\uC774 \uACF5\uAC1C \uC2A4\uD0A4\uB9C8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
4112
5257
  const semanticErrors = inspectAuthoringSemantics(manifest, form);
4113
- if (semanticErrors.length) invalid(semanticErrors.map((error2) => `${error2.path}: ${error2.message}`).join(" / "));
5258
+ if (semanticErrors.length) invalid2(semanticErrors.map((error2) => `${error2.path}: ${error2.message}`).join(" / "));
4114
5259
  const template = output2.template;
4115
- if (template.root !== "runtime" || typeof template.entry !== "string") invalid("runtime \uD15C\uD50C\uB9BF \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
5260
+ if (template.root !== "runtime" || typeof template.entry !== "string") invalid2("runtime \uD15C\uD50C\uB9BF \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
4116
5261
  file(`runtime/${template.entry}`);
4117
5262
  const runtime2 = manifest.runtime;
4118
5263
  for (const profile of runtime2.profiles) file(`runtime/.clack/profiles/${profile}.yaml`, 16 * 1024);
4119
5264
  const examples = Object.keys(extracted).filter((path) => /^examples\/[^/]+\.json$/.test(path));
4120
- if (!examples.length || examples.length > 10) invalid("\uD15C\uD50C\uB9BF \uC608\uC2DC\uB294 1~10\uAC1C \uD544\uC694\uD569\uB2C8\uB2E4.");
5265
+ if (!examples.length || examples.length > 10) invalid2("\uD15C\uD50C\uB9BF \uC608\uC2DC\uB294 1~10\uAC1C \uD544\uC694\uD569\uB2C8\uB2E4.");
4121
5266
  let validateExample;
4122
5267
  try {
4123
- const examplesAjv = new Ajv2020({ allErrors: true, strict: false });
5268
+ const examplesAjv = new Ajv20202({ allErrors: true, strict: false });
4124
5269
  examplesAjv.addFormat("uuid", /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
4125
5270
  validateExample = examplesAjv.compile(form);
4126
5271
  } catch {
4127
- return invalid("\uC81C\uC791 \uD3FC JSON Schema\uB97C \uD655\uC778\uD558\uC138\uC694.");
5272
+ return invalid2("\uC81C\uC791 \uD3FC JSON Schema\uB97C \uD655\uC778\uD558\uC138\uC694.");
4128
5273
  }
4129
5274
  for (const path of examples) if (!validateExample(json(file(path, 64 * 1024), 64 * 1024, "\uC608\uC2DC"))) {
4130
- invalid("\uC81C\uC791 \uD3FC\uACFC \uC608\uC2DC \uB370\uC774\uD130\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
5275
+ invalid2("\uC81C\uC791 \uD3FC\uACFC \uC608\uC2DC \uB370\uC774\uD130\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
4131
5276
  }
4132
5277
  }
4133
- return { manifest, file_count: Object.keys(extracted).length, total_bytes: totalBytes };
5278
+ return {
5279
+ manifest,
5280
+ file_count: Object.keys(extracted).length,
5281
+ total_bytes: totalBytes,
5282
+ ...editor ? { editor: editor.summary, warnings: editor.warnings } : {}
5283
+ };
5284
+ }
5285
+ function fillEditorHash(files) {
5286
+ const manifestBytes = files["clack.skill.json"];
5287
+ if (!manifestBytes) return null;
5288
+ let text;
5289
+ let manifest;
5290
+ try {
5291
+ text = textDecoder2.decode(manifestBytes);
5292
+ manifest = JSON.parse(text);
5293
+ } catch {
5294
+ return null;
5295
+ }
5296
+ if (!object(manifest)) return null;
5297
+ const computed = computeEditorHash(manifest, files);
5298
+ const editor = object(manifest.authoring) ? manifest.authoring.editor : void 0;
5299
+ if (!computed || !object(editor) || typeof editor.sha256 !== "string" || editor.sha256 === computed) return null;
5300
+ const declared = editor.sha256;
5301
+ const next = text.split(declared).length === 2 ? text.replace(declared, computed) : `${JSON.stringify({ ...manifest, authoring: { ...manifest.authoring, editor: { ...editor, sha256: computed } } }, null, 2)}
5302
+ `;
5303
+ return new TextEncoder().encode(next);
4134
5304
  }
4135
- async function prepareSkillPackage(source) {
5305
+ async function prepareSkillPackage(source, options2 = {}) {
4136
5306
  const path = resolve7(source);
4137
5307
  let info;
4138
5308
  try {
4139
5309
  info = await lstat4(path);
4140
5310
  } catch {
4141
- return invalid("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5311
+ return invalid2("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4142
5312
  }
4143
- if (info.isSymbolicLink()) invalid("\uC2A4\uD0AC \uACBD\uB85C\uB294 \uC2EC\uBCFC\uB9AD \uB9C1\uD06C\uC77C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4144
- let bytes;
5313
+ if (info.isSymbolicLink()) invalid2("\uC2A4\uD0AC \uACBD\uB85C\uB294 \uC2EC\uBCFC\uB9AD \uB9C1\uD06C\uC77C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5314
+ let bytes2;
5315
+ let filledManifest;
4145
5316
  if (info.isDirectory()) {
4146
5317
  const files = await collectFiles(path);
4147
- if (!Object.keys(files).length) invalid("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");
4148
- bytes = Buffer.from(zipSync(files, { level: 6, mtime: new Date(2e3, 0, 1) }));
4149
- } else if (info.isFile() && extname2(path).toLowerCase() === ".zip") bytes = await readRegular(path);
4150
- else return invalid("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.");
4151
- const inspected = inspectZip(bytes, info.isDirectory() ? basename4(path) : void 0);
4152
- return { bytes, sha256: hash(bytes), ...inspected };
5318
+ if (!Object.keys(files).length) invalid2("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");
5319
+ if (options2.fillEditorHash) {
5320
+ const filled = fillEditorHash(files);
5321
+ if (filled) {
5322
+ files["clack.skill.json"] = filled;
5323
+ filledManifest = filled;
5324
+ }
5325
+ }
5326
+ bytes2 = Buffer.from(zipSync(files, { level: 6, mtime: new Date(2e3, 0, 1) }));
5327
+ } else if (info.isFile() && extname2(path).toLowerCase() === ".zip") bytes2 = await readRegular(path);
5328
+ else return invalid2("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.");
5329
+ const inspected = inspectZip(
5330
+ bytes2,
5331
+ info.isDirectory() ? basename4(path) : void 0,
5332
+ info.isDirectory() && !options2.fillEditorHash ? "warn" : "error"
5333
+ );
5334
+ return { bytes: bytes2, sha256: hash(bytes2), ...inspected, ...filledManifest ? { filled_manifest: filledManifest } : {} };
4153
5335
  }
4154
5336
 
4155
5337
  // src/commands/skill.ts
@@ -4192,7 +5374,7 @@ function ensurePat(client, message = PAT_FOR_WRITE) {
4192
5374
  }
4193
5375
  return token;
4194
5376
  }
4195
- async function validateSkillRemotely(client, bytes) {
5377
+ async function validateSkillRemotely(client, bytes2) {
4196
5378
  const token = ensurePat(client);
4197
5379
  const url = new URL("/v4/creator/skills/validate", client.settings.baseUrl);
4198
5380
  let response;
@@ -4210,7 +5392,7 @@ async function validateSkillRemotely(client, bytes) {
4210
5392
  "X-CLACK-Time-Contract": "utc-v1",
4211
5393
  "Cache-Control": "no-store"
4212
5394
  },
4213
- body: new Uint8Array(bytes),
5395
+ body: new Uint8Array(bytes2),
4214
5396
  redirect: "error",
4215
5397
  signal: AbortSignal.timeout(3e4)
4216
5398
  });
@@ -4296,13 +5478,13 @@ function registerSkillCommands(program2, runtime2) {
4296
5478
  }, contract });
4297
5479
  if (!Array.isArray(result.data)) throw new CliError("\uC2A4\uD0AC \uBAA9\uB85D \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 502);
4298
5480
  if (!opts.all) {
4299
- ctx.output(result);
5481
+ ctx.output({ ...result, data: markCenterOnly(result.data) });
4300
5482
  return;
4301
5483
  }
4302
5484
  contract = result.time_contract;
4303
5485
  all.push(...result.data);
4304
5486
  if (!result.pagination?.has_more) {
4305
- ctx.output({ data: all, time_contract: result.time_contract });
5487
+ ctx.output({ data: markCenterOnly(all), time_contract: result.time_contract });
4306
5488
  return;
4307
5489
  }
4308
5490
  const next = result.pagination.next_cursor;
@@ -4316,7 +5498,8 @@ function registerSkillCommands(program2, runtime2) {
4316
5498
  runtime2.action(skill.command("get <slug>").description("\uC2A4\uD0AC \uC0C1\uC138 \uC870\uD68C (skill:read)"), async (ctx, [value2]) => {
4317
5499
  const parsed = skillSlug.safeParse(value2);
4318
5500
  if (!parsed.success) throw new CliError("\uC2A4\uD0AC \uC774\uB984\uC744 \uD655\uC778\uD558\uC138\uC694.");
4319
- ctx.output(await ctx.api.request("GET", `/v4/skills/${parsed.data}`));
5501
+ const detail = await ctx.api.request("GET", `/v4/skills/${parsed.data}`);
5502
+ ctx.output({ ...detail, data: markCenterOnly(detail.data) });
4320
5503
  });
4321
5504
  runtime2.action(skill.command("form <slug> <version>").description("\uC2A4\uD0AC \uBC84\uC804 \uC785\uB825 \uC591\uC2DD \uC870\uD68C (skill:read)"), async (ctx, [value2, version]) => {
4322
5505
  const parsed = skillSlug.safeParse(value2);
@@ -4330,27 +5513,73 @@ function registerSkillCommands(program2, runtime2) {
4330
5513
  });
4331
5514
  runtime2.action(skill.command("validate [dir]").description("\uC2A4\uD0AC \uD328\uD0A4\uC9C0 \uB85C\uCEEC \uAC80\uC0AC (\uB85C\uADF8\uC778 \uBD88\uD544\uC694, --remote\uB294 skill:write)").option("--remote", "\uC11C\uBC84\uC5D0\uC11C\uB3C4 \uD328\uD0A4\uC9C0\uB97C \uB4DC\uB77C\uC774\uB7F0 \uAC80\uC99D (skill:write)"), async (ctx, [dir], opts) => {
4332
5515
  const prepared = await prepareSkillPackage(dir ?? ".");
4333
- if (opts.remote && !ctx.options.dryRun) {
4334
- const remote = await validateSkillRemotely(ctx.api, prepared.bytes);
4335
- ctx.output({
4336
- local_valid: true,
4337
- sha256: prepared.sha256,
4338
- byte_size: prepared.bytes.length,
4339
- file_count: prepared.file_count,
4340
- manifest: prepared.manifest,
4341
- remote
4342
- });
4343
- } else ctx.output({
5516
+ const local = {
4344
5517
  local_valid: true,
4345
5518
  sha256: prepared.sha256,
4346
5519
  byte_size: prepared.bytes.length,
4347
5520
  file_count: prepared.file_count,
4348
5521
  manifest: prepared.manifest,
4349
- ...opts.remote ? { remote_skipped: true } : {}
5522
+ ...editorReport(prepared)
5523
+ };
5524
+ if (opts.remote && !ctx.options.dryRun) {
5525
+ const remote = await validateSkillRemotely(ctx.api, prepared.bytes);
5526
+ ctx.output({ ...local, remote });
5527
+ } else ctx.output({ ...local, ...opts.remote ? { remote_skipped: true } : {} });
5528
+ });
5529
+ 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) => {
5530
+ const source = dir ?? ".";
5531
+ const prepared = await prepareSkillPackage(source, { fillEditorHash: true });
5532
+ const out = resolve8(opts.out ?? `${prepared.manifest.name}-${prepared.manifest.version}.zip`);
5533
+ const writeManifest = Boolean(opts.writeManifest && prepared.filled_manifest);
5534
+ if (!ctx.options.dryRun) {
5535
+ await writeFile2(out, prepared.bytes);
5536
+ if (writeManifest) await writeFile2(join3(resolve8(source), "clack.skill.json"), prepared.filled_manifest);
5537
+ }
5538
+ ctx.output({
5539
+ ...ctx.options.dryRun ? { dry_run: true } : {},
5540
+ out,
5541
+ sha256: prepared.sha256,
5542
+ byte_size: prepared.bytes.length,
5543
+ file_count: prepared.file_count,
5544
+ slug: prepared.manifest.name,
5545
+ version: prepared.manifest.version,
5546
+ ...prepared.filled_manifest ? { editor_hash_filled: true, manifest_written: writeManifest && !ctx.options.dryRun } : {},
5547
+ ...editorReport(prepared)
4350
5548
  });
4351
5549
  });
5550
+ const editor = skill.command("editor").description("\uC2A4\uD0AC \uC5D0\uB514\uD130(authoring.editor) \uAC1C\uBC1C \uB3C4\uAD6C \u2014 \uC5D0\uB514\uD130 \uC2A4\uD0AC\uC740 \uD06C\uB9AC\uC5D0\uC774\uD130 \uC13C\uD130 \uC804\uC6A9");
5551
+ runtime2.action(
5552
+ editor.command("dev").description("\uB85C\uCEEC \uBAA8\uC758 \uD638\uC2A4\uD2B8\uC5D0\uC11C \uC5D0\uB514\uD130\uB97C \uB744\uC6B4\uB2E4 (http://localhost:5170, \uB85C\uADF8\uC778 \uBD88\uD544\uC694)").option("--editor <dir>", "\uC5D0\uB514\uD130 \uBC88\uB4E4 \uB514\uB809\uD130\uB9AC (\uAE30\uBCF8: SDK \uC2A4\uD0C0\uD130)").option("--document <file>", "\uCD08\uAE30 \uB370\uC774\uD130 \uBB38\uC11C JSON (\uAE30\uBCF8: SDK \uC2A4\uD0C0\uD130 \uC608\uC2DC)").option("--schema <file>", "\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8 output/data.schema.json (\uAE30\uBCF8: SDK \uC2A4\uD0C0\uD130)").option("--host-port <port>", "\uD638\uC2A4\uD2B8 \uD398\uC774\uC9C0 \uD3EC\uD2B8", "5170").option("--editor-port <port>", "\uC5D0\uB514\uD130 \uD3EC\uD2B8", "5171").option("--sdk-dir <dir>", "@clack/skill-editor-sdk \uC704\uCE58 (\uAE30\uBCF8: CLACK_SKILL_EDITOR_SDK_DIR, \uC124\uCE58\uB41C \uD328\uD0A4\uC9C0, CLACK_MONOREPO_DIR/clack-skill-editor-sdk \uC21C)").addHelpText("after", `
5553
+ ${SDK_LOCATION_HELP}
5554
+ `),
5555
+ async (ctx, _args, opts) => {
5556
+ const args = [];
5557
+ for (const [flag, value2] of [
5558
+ ["editor", opts.editor],
5559
+ ["document", opts.document],
5560
+ ["schema", opts.schema],
5561
+ ["host-port", opts.hostPort],
5562
+ ["editor-port", opts.editorPort]
5563
+ ]) {
5564
+ if (value2 === void 0) continue;
5565
+ 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.");
5566
+ args.push(`--${flag}`, flag.endsWith("port") ? value2 : resolve8(value2));
5567
+ }
5568
+ const sdkDir = resolveSdkDir(opts.sdkDir);
5569
+ const serve = join3(sdkDir, "mock-host/serve.mjs");
5570
+ if (ctx.options.dryRun) {
5571
+ ctx.output({ dry_run: true, sdk_dir: sdkDir, command: ["node", serve, ...args] });
5572
+ return;
5573
+ }
5574
+ await new Promise((done, fail2) => {
5575
+ const child = spawn(process.execPath, [serve, ...args], { stdio: "inherit" });
5576
+ 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")));
5577
+ 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());
5578
+ });
5579
+ }
5580
+ );
4352
5581
  runtime2.action(skill.command("push [dir]").description("\uC2A4\uD0AC \uD328\uD0A4\uC9C0 \uAC80\uC99D \uD6C4 \uC0C8 \uBC84\uC804 \uC5C5\uB85C\uB4DC (skill:write)").option("--skill-id <id>", "\uAE30\uC874 \uC2A4\uD0AC ID\uC5D0 \uC0C8 \uBC84\uC804 \uCD94\uAC00"), async (ctx, [dir], opts) => {
4353
- const prepared = await prepareSkillPackage(dir ?? ".");
5582
+ const prepared = await prepareSkillPackage(dir ?? ".", { fillEditorHash: true });
4354
5583
  if (prepared.manifest.type !== "instruction" && prepared.manifest.type !== "template") {
4355
5584
  throw new CliError("\uD604\uC7AC \uC2A4\uD0AC \uC5C5\uB85C\uB4DC\uB294 instruction\xB7template \uC720\uD615\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.", "SKILL_TYPE_UNSUPPORTED");
4356
5585
  }
@@ -4454,6 +5683,39 @@ function registerSkillCommands(program2, runtime2) {
4454
5683
  }
4455
5684
  );
4456
5685
  }
5686
+ function markCenterOnly(value2) {
5687
+ const mark2 = (item) => {
5688
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
5689
+ const row = item;
5690
+ return row.center_only === true || row.editor === true ? { ...row, center_only: true, availability: "\uC13C\uD130 \uC804\uC6A9" } : row;
5691
+ };
5692
+ return Array.isArray(value2) ? value2.map(mark2) : mark2(value2);
5693
+ }
5694
+ function editorReport(prepared) {
5695
+ if (!prepared.editor) return {};
5696
+ return {
5697
+ editor: prepared.editor,
5698
+ center_only: true,
5699
+ ...prepared.warnings?.length ? { warnings: prepared.warnings } : {},
5700
+ notice: "\uB85C\uCEEC \uAC80\uC0AC\uB294 \uAD6C\uC870\xB7\uD55C\uB3C4\xB7\uD574\uC2DC\xB7\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8\xB7\uC608\uC2DC\uAE4C\uC9C0\uC785\uB2C8\uB2E4. \uC5D0\uB514\uD130 \uCF54\uB4DC \uC815\uC801 \uAC80\uC0AC(editor-static-v1)\uC640 \uC2EC\uC0AC\uB294 \uC11C\uBC84 \uD310\uC815\uC774 \uCD5C\uC885\uC785\uB2C8\uB2E4."
5701
+ };
5702
+ }
5703
+ 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.";
5704
+ function resolveSdkDir(option) {
5705
+ const candidates = [];
5706
+ if (option) candidates.push(resolve8(option));
5707
+ else {
5708
+ if (process.env.CLACK_SKILL_EDITOR_SDK_DIR) candidates.push(resolve8(process.env.CLACK_SKILL_EDITOR_SDK_DIR));
5709
+ try {
5710
+ candidates.push(resolve8(createRequire(join3(process.cwd(), "noop.js")).resolve("@clack/skill-editor-sdk/package.json"), ".."));
5711
+ } catch {
5712
+ }
5713
+ if (process.env.CLACK_MONOREPO_DIR) candidates.push(resolve8(process.env.CLACK_MONOREPO_DIR, "clack-skill-editor-sdk"));
5714
+ }
5715
+ const found = candidates.find((dir) => existsSync(join3(dir, "mock-host/serve.mjs")));
5716
+ 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");
5717
+ return found;
5718
+ }
4457
5719
  function skillVersionIds(skillId, versionId) {
4458
5720
  const parsedSkillId = uuid2.safeParse(skillId);
4459
5721
  const parsedVersionId = uuid2.safeParse(versionId);
@@ -4631,7 +5893,7 @@ var toolDefinitions = [
4631
5893
  function error(id2, code, message) {
4632
5894
  return { jsonrpc: "2.0", id: id2, error: { code, message } };
4633
5895
  }
4634
- function isObject2(value2) {
5896
+ function isObject3(value2) {
4635
5897
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
4636
5898
  }
4637
5899
  function toolResult(value2, isError = false) {
@@ -4650,7 +5912,7 @@ var PlatformMcpServer = class {
4650
5912
  env;
4651
5913
  initialized = false;
4652
5914
  async handle(message) {
4653
- if (!isObject2(message) || message.jsonrpc !== "2.0" || typeof message.method !== "string" || message.id !== void 0 && typeof message.id !== "string" && typeof message.id !== "number" && message.id !== null) {
5915
+ if (!isObject3(message) || message.jsonrpc !== "2.0" || typeof message.method !== "string" || message.id !== void 0 && typeof message.id !== "string" && typeof message.id !== "number" && message.id !== null) {
4654
5916
  return error(null, -32600, "\uC62C\uBC14\uB978 JSON-RPC \uC694\uCCAD\uC774 \uC544\uB2D9\uB2C8\uB2E4.");
4655
5917
  }
4656
5918
  const request = message;
@@ -4659,7 +5921,7 @@ var PlatformMcpServer = class {
4659
5921
  return void 0;
4660
5922
  }
4661
5923
  if (request.method === "initialize") {
4662
- const requested = isObject2(request.params) && typeof request.params.protocolVersion === "string" ? request.params.protocolVersion : "";
5924
+ const requested = isObject3(request.params) && typeof request.params.protocolVersion === "string" ? request.params.protocolVersion : "";
4663
5925
  if (!requested) return error(request.id, -32602, "\uD504\uB85C\uD1A0\uCF5C \uBC84\uC804\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
4664
5926
  this.initialized = false;
4665
5927
  return { jsonrpc: "2.0", id: request.id, result: {
@@ -4672,7 +5934,7 @@ var PlatformMcpServer = class {
4672
5934
  if (!this.initialized) return error(request.id, -32e3, "MCP \uCD08\uAE30\uD654\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
4673
5935
  if (request.method === "tools/list") return { jsonrpc: "2.0", id: request.id, result: { tools: toolDefinitions } };
4674
5936
  if (request.method !== "tools/call") return error(request.id, -32601, "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 MCP \uBA54\uC11C\uB4DC\uC785\uB2C8\uB2E4.");
4675
- if (!isObject2(request.params) || typeof request.params.name !== "string" || request.params.arguments !== void 0 && !isObject2(request.params.arguments)) {
5937
+ if (!isObject3(request.params) || typeof request.params.name !== "string" || request.params.arguments !== void 0 && !isObject3(request.params.arguments)) {
4676
5938
  return error(request.id, -32602, "\uB3C4\uAD6C \uC774\uB984\uACFC \uC778\uC790\uB97C \uD655\uC778\uD558\uC138\uC694.");
4677
5939
  }
4678
5940
  const name2 = request.params.name;
@@ -4682,7 +5944,7 @@ var PlatformMcpServer = class {
4682
5944
  const writeKind = name2 === "platform_server_data_put" ? "put" : name2 === "platform_server_data_patch" ? "patch" : name2 === "platform_server_data_delete" ? "delete" : null;
4683
5945
  if (writeKind) {
4684
5946
  const preview = platformWritePreview(writeKind, args);
4685
- if (!isObject2(args) || args.confirm !== true) {
5947
+ if (!isObject3(args) || args.confirm !== true) {
4686
5948
  return { jsonrpc: "2.0", id: request.id, result: toolResult({ preview: true, confirmation_required: true, ...preview }) };
4687
5949
  }
4688
5950
  }
@@ -4700,7 +5962,7 @@ var PlatformMcpServer = class {
4700
5962
  }
4701
5963
  /** 개인 액세스 토큰(platform:read·platform:write, skill:*, creator-content:write·publish 스코프) 기반 도구. */
4702
5964
  async callPatTool(name2, args) {
4703
- if (patDestructiveToolNames.has(name2) && (!isObject2(args) || args.confirm !== true)) {
5965
+ if (patDestructiveToolNames.has(name2) && (!isObject3(args) || args.confirm !== true)) {
4704
5966
  return { preview: true, confirmation_required: true, tool: name2, arguments: redact(args) };
4705
5967
  }
4706
5968
  const client = platformPatClient(this.settings, this.env);
@@ -4715,7 +5977,7 @@ var PlatformMcpServer = class {
4715
5977
  }
4716
5978
  if (name2 === "platform_skill_push") {
4717
5979
  const { dir, skill_id } = parsePat(patSchemas.skillPush, args, "\uC2A4\uD0AC \uC5C5\uB85C\uB4DC \uC785\uB825");
4718
- const prepared = await prepareSkillPackage(dir ?? ".");
5980
+ const prepared = await prepareSkillPackage(dir ?? ".", { fillEditorHash: true });
4719
5981
  if (prepared.manifest.type !== "instruction" && prepared.manifest.type !== "template") {
4720
5982
  throw new CliError("\uD604\uC7AC \uC2A4\uD0AC \uC5C5\uB85C\uB4DC\uB294 instruction\xB7template \uC720\uD615\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.", "SKILL_TYPE_UNSUPPORTED");
4721
5983
  }
@@ -4865,7 +6127,7 @@ var revision3 = (value2) => parse(z11.coerce.number().int().nonnegative().max(42
4865
6127
  var base = "/v4/creator/custom-pages";
4866
6128
  var presentationSchema = z11.object({
4867
6129
  schema_version: z11.literal(1),
4868
- header_mode: z11.enum(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]),
6130
+ header_mode: z11.enum(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close", "floating_close_safe_area"]),
4869
6131
  color_scheme: z11.enum(["light", "dark"])
4870
6132
  }).strict();
4871
6133
  function registerPageCommands(program2, runtime2) {
@@ -4877,7 +6139,7 @@ function registerPageCommands(program2, runtime2) {
4877
6139
  const policy = parse(z11.literal("2026-09-22"), opts.policyVersion);
4878
6140
  await mutate(ctx, "POST", base, { target_type: opts.target, ...opts.target === "space" ? { space_id: uuid3(opts.spaceId) } : {}, policy_version: policy });
4879
6141
  });
4880
- runtime2.action(page.command("upload <id> <file>").description("HTML/ZIP \uC5C5\uB85C\uB4DC\uB85C \uC0C8 \uBE44\uACF5\uAC1C \uBC84\uC804 \uC0DD\uC131 (custom-page:write)").addOption(new Option3("--header <mode>", "\uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]).default("fixed")).addOption(new Option3("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).default("light")), async (ctx, [id2, file], opts) => {
6142
+ 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) => {
4881
6143
  const path = `${base}/${uuid3(id2)}`;
4882
6144
  const prepared = await prepareContentFile(file);
4883
6145
  const presentation = parse(presentationSchema, { schema_version: 1, header_mode: opts.header, color_scheme: opts.color });
@@ -4895,7 +6157,7 @@ function registerPageCommands(program2, runtime2) {
4895
6157
  }
4896
6158
  });
4897
6159
  runtime2.action(page.command("complete <id> <upload-id>").description("\uC5C5\uB85C\uB4DC \uC644\uB8CC \uCC98\uB9AC \uC7AC\uC2DC\uB3C4 (custom-page:write)"), async (ctx, [id2, uploadId]) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/uploads/${uuid3(uploadId)}/complete`, {}));
4898
- runtime2.action(page.command("presentation <id> <version-id>").description("\uD30C\uC77C\uC740 \uB450\uACE0 \uD5E4\uB354 \uC124\uC815\uB9CC \uBC14\uAFBC \uC0C8 \uBC84\uC804 \uC0DD\uC131 (custom-page:write)").addOption(new Option3("--header <mode>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]).makeOptionMandatory()).addOption(new Option3("--color <scheme>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).makeOptionMandatory()), async (ctx, [id2, versionId], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/presentation`, {
6160
+ runtime2.action(page.command("presentation <id> <version-id>").description("\uD30C\uC77C\uC740 \uB450\uACE0 \uD5E4\uB354 \uC124\uC815\uB9CC \uBC14\uAFBC \uC0C8 \uBC84\uC804 \uC0DD\uC131 (custom-page:write)").addOption(new Option3("--header <mode>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close", "floating_close_safe_area"]).makeOptionMandatory()).addOption(new Option3("--color <scheme>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).makeOptionMandatory()), async (ctx, [id2, versionId], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/presentation`, {
4899
6161
  presentation: parse(presentationSchema, { schema_version: 1, header_mode: opts.header, color_scheme: opts.color })
4900
6162
  }));
4901
6163
  runtime2.action(page.command("preview <id> <version-id>").description("\uC571 \uD655\uC778\uC6A9 \uBBF8\uB9AC\uBCF4\uAE30 \uB9C1\uD06C \uC870\uD68C (custom-page:read)"), async (ctx, [id2, versionId]) => ctx.output(await ctx.api.request("GET", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/preview`)));