@clack-platform/cli 0.1.0 → 0.2.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 +68 -13
  2. package/dist/cli.js +1419 -232
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -86,6 +86,20 @@ function requestTimeContract(method, path) {
86
86
  ].includes(path) || /^\/v4\/posts\/\d+(?:\/comments|\/merchandises)?$/.test(path) || /^\/v4\/merchandises\/\d+$/.test(path) || /^\/v4\/channels\/[^/]+(?:\/posts|\/series)?$/.test(path) || /^\/v4\/(?:channel-posts|channel-series)\/\d+$/.test(path) || /^\/v4\/search\/(?:posts|merchandises-with-post|merchandises-without-post|channel-posts|channels)$/.test(path)) return "utc-v1";
87
87
  return "legacy-kst";
88
88
  }
89
+ var KST_PARTS = new Intl.DateTimeFormat("en-CA", {
90
+ timeZone: "Asia/Seoul",
91
+ year: "numeric",
92
+ month: "2-digit",
93
+ day: "2-digit",
94
+ hour: "2-digit",
95
+ minute: "2-digit",
96
+ hour12: false
97
+ });
98
+ function formatKst(date) {
99
+ const parts = Object.fromEntries(KST_PARTS.formatToParts(date).map((part) => [part.type, part.value]));
100
+ const hour = parts.hour === "24" ? "00" : parts.hour;
101
+ return `${parts.year}-${parts.month}-${parts.day} ${hour}:${parts.minute} KST`;
102
+ }
89
103
  function formatInstant(value2, contract, mode) {
90
104
  const iso = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(value2);
91
105
  const legacyWallClock = contract === "legacy-kst" && /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d{3})?$/.test(value2);
@@ -93,10 +107,7 @@ function formatInstant(value2, contract, mode) {
93
107
  const source = contract === "legacy-kst" ? (iso ? value2.slice(0, -1) : value2.replace(" ", "T")) + "+09:00" : value2;
94
108
  const date = new Date(source);
95
109
  if (!Number.isFinite(date.getTime())) return value2;
96
- return mode === "utc" ? date.toISOString() : new Intl.DateTimeFormat("ko-KR", {
97
- dateStyle: "medium",
98
- timeStyle: "long"
99
- }).format(date);
110
+ return mode === "utc" ? date.toISOString() : formatKst(date);
100
111
  }
101
112
  var instantKeys = /* @__PURE__ */ new Set(["created_at", "updated_at", "published_at", "hidden_at", "owner_hidden_at", "delete_available_at", "lastBulkBumpedAt", "deleted_at", "expires_at", "revoked_at", "suspended_at", "last_used_at"]);
102
113
  function displayData(value2, contract, mode) {
@@ -139,7 +150,7 @@ function redact(value2) {
139
150
  // package.json
140
151
  var package_default = {
141
152
  name: "@clack-platform/cli",
142
- version: "0.1.0",
153
+ version: "0.2.0",
143
154
  description: "\uD074\uB799 \uACC4\uC815\uC758 \uC0C1\uD488\xB7\uCF58\uD150\uCE20\xB7\uD504\uB85C\uD544\uC744 \uAD00\uB9AC\uD558\uB294 CLI",
144
155
  type: "module",
145
156
  bin: {
@@ -388,9 +399,9 @@ function documentPath(collection2, key) {
388
399
  return `/platform/v1/data/${collection2}/docs/${key}`;
389
400
  }
390
401
  function bodySize(body) {
391
- const bytes = Buffer.byteLength(JSON.stringify(body), "utf8");
392
- if (bytes > maxBodyBytes) throw new CliError("\uBB38\uC11C \uBCF8\uBB38\uC740 64 KiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.", "VALIDATION_ERROR");
393
- return bytes;
402
+ const bytes2 = Buffer.byteLength(JSON.stringify(body), "utf8");
403
+ if (bytes2 > maxBodyBytes) throw new CliError("\uBB38\uC11C \uBCF8\uBB38\uC740 64 KiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.", "VALIDATION_ERROR");
404
+ return bytes2;
394
405
  }
395
406
  async function readPlatformBodyFile(file) {
396
407
  let raw;
@@ -587,11 +598,11 @@ async function authoringRequest(client, raw, surface = "cli") {
587
598
  if (input.action === "upload") {
588
599
  const stat3 = await lstat2(input.file);
589
600
  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.");
590
- const bytes = await readFile2(input.file);
601
+ const bytes2 = await readFile2(input.file);
591
602
  const body2 = new FormData();
592
603
  body2.append("field", input.field);
593
604
  if (input.item_index !== void 0) body2.append("item_index", String(input.item_index));
594
- body2.append("file", new Blob([bytes]), basename(input.file));
605
+ body2.append("file", new Blob([bytes2]), basename(input.file));
595
606
  return client.request("POST", `${path}/assets`, { body: body2 });
596
607
  }
597
608
  if (input.action === "image" || input.action === "image-batch") {
@@ -606,7 +617,7 @@ async function authoringRequest(client, raw, surface = "cli") {
606
617
  return client.request(action === "save" ? "PATCH" : "POST", `${path}/${action === "save" ? "form" : action}`, { body });
607
618
  }
608
619
  function registerAuthoringCommands(program2, runtime2) {
609
- runtime2.action(program2.command("authoring").description("\uACF5\uAC1C \uC2A4\uD0AC\uB85C \uC81C\uC791\xB7\uC790\uC0B0 \uC5C5\uB85C\uB4DC\xB7\uC774\uBBF8\uC9C0 \uC0DD\uC131\xB7\uD328\uD0A4\uC9D5").requiredOption("--input <file>", "action\uACFC \uC694\uCCAD \uAC12\uC774 \uC788\uB294 JSON \uD30C\uC77C"), async (ctx, _args, opts) => {
620
+ runtime2.action(program2.command("authoring").description("\uACF5\uAC1C \uC2A4\uD0AC\uB85C \uC81C\uC791\xB7\uC790\uC0B0 \uC5C5\uB85C\uB4DC\xB7\uC774\uBBF8\uC9C0 \uC0DD\uC131\xB7\uD328\uD0A4\uC9D5 (creator-content:write)").requiredOption("--input <file>", "action\uACFC \uC694\uCCAD \uAC12\uC774 \uC788\uB294 JSON \uD30C\uC77C"), async (ctx, _args, opts) => {
610
621
  const input = await readPlatformBodyFile(opts.input);
611
622
  if (ctx.options.dryRun) {
612
623
  const parsed = authoringInput.safeParse(input);
@@ -667,7 +678,7 @@ var ConfigStore = class {
667
678
  try {
668
679
  const stat3 = await lstat3(path);
669
680
  if (!stat3.isFile() || stat3.isSymbolicLink()) throw new CliError("\uC124\uC815 \uD30C\uC77C\uC740 \uC77C\uBC18 \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.", "CONFIG_UNSAFE");
670
- if (name2 === "credentials.json" && process.platform !== "win32") await chmod(path, 384);
681
+ if ((name2 === "credentials.json" || name2 === "device-request.json") && process.platform !== "win32") await chmod(path, 384);
671
682
  const body = JSON.parse(await readFile3(path, "utf8"));
672
683
  if (!body || typeof body !== "object" || !("profiles" in body) || !body.profiles || typeof body.profiles !== "object" || Array.isArray(body.profiles)) throw new Error("\uD615\uC2DD");
673
684
  return body;
@@ -737,6 +748,21 @@ var ConfigStore = class {
737
748
  delete all.profiles[validateProfile(profile)];
738
749
  await this.write("credentials.json", all);
739
750
  }
751
+ /** 발급한 디바이스 요청을 저장해 `login`이 같은 턴에서 끝나도 `login --resume`으로 이어받을 수 있게 한다(E1). */
752
+ async savePendingDeviceRequest(profile, request) {
753
+ const all = await this.read("device-request.json");
754
+ all.profiles[validateProfile(profile)] = request;
755
+ await this.write("device-request.json", all);
756
+ }
757
+ async getPendingDeviceRequest(profile) {
758
+ return (await this.read("device-request.json")).profiles[validateProfile(profile)];
759
+ }
760
+ /** 만료·거부·소비 등으로 더 이상 재개할 수 없는 요청을 정리한다. 네트워크 오류처럼 일시적인 실패에는 호출하지 않는다. */
761
+ async removePendingDeviceRequest(profile) {
762
+ const all = await this.read("device-request.json");
763
+ delete all.profiles[validateProfile(profile)];
764
+ await this.write("device-request.json", all);
765
+ }
740
766
  async getConfig(profile) {
741
767
  return (await this.read("config.json")).profiles[validateProfile(profile)] ?? {};
742
768
  }
@@ -846,6 +872,57 @@ async function pollDevice(api, request, deps = {
846
872
  }
847
873
  throw new CliError("\uC2B9\uC778 \uCF54\uB4DC\uAC00 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. clack login\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.", "EXPIRED_TOKEN", 410);
848
874
  }
875
+ var TERMINAL_DEVICE_FAILURE_CODES = ["EXPIRED_TOKEN", "ACCESS_DENIED", "DEVICE_CODE_CONSUMED", "DEVICE_AUTHORIZATION_NOT_FOUND"];
876
+ function validateVerificationUrl(href2) {
877
+ let verification;
878
+ try {
879
+ verification = new URL(href2);
880
+ } catch {
881
+ throw new CliError("\uC2B9\uC778 \uD398\uC774\uC9C0 \uC8FC\uC18C\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 0);
882
+ }
883
+ if (verification.protocol !== "https:" || !["clack.kr", "dev.clack.kr", "www.clack.kr"].includes(verification.hostname) || verification.username || verification.password || verification.port) throw new CliError("\uC2B9\uC778 \uD398\uC774\uC9C0 \uC8FC\uC18C\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 0);
884
+ return verification;
885
+ }
886
+ function validateFreshDeviceRequest(request) {
887
+ if (!/^dvc_[a-f0-9]{64}$/.test(request.device_code) || !/^[A-Z2-9]{4}-[A-Z2-9]{4}$/.test(request.user_code) || !Number.isFinite(request.expires_in) || request.expires_in <= 0 || request.expires_in > 600 || !Number.isFinite(request.interval) || request.interval < 1) throw new CliError("\uC2B9\uC778 \uC694\uCCAD \uC751\uB2F5\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 0);
888
+ return validateVerificationUrl(request.verification_uri_complete);
889
+ }
890
+ function validatePendingRequest(pending) {
891
+ if (!/^dvc_[a-f0-9]{64}$/.test(pending.device_code) || !/^[A-Z2-9]{4}-[A-Z2-9]{4}$/.test(pending.user_code) || !Number.isFinite(pending.interval) || pending.interval < 1 || !Number.isFinite(Date.parse(pending.expires_at))) {
892
+ throw new CliError("\uC800\uC7A5\uB41C \uB85C\uADF8\uC778 \uC694\uCCAD \uC815\uBCF4\uB97C \uD655\uC778\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. clack login\uC73C\uB85C \uC0C8\uB85C \uC2DC\uC791\uD558\uC138\uC694.", "CONFIG_UNSAFE");
893
+ }
894
+ return validateVerificationUrl(pending.verification_uri_complete);
895
+ }
896
+ function newPendingRequest(request, scopes, baseUrl) {
897
+ return {
898
+ device_code: request.device_code,
899
+ user_code: request.user_code,
900
+ verification_uri: request.verification_uri,
901
+ verification_uri_complete: request.verification_uri_complete,
902
+ interval: request.interval,
903
+ scopes,
904
+ base_url: baseUrl,
905
+ expires_at: new Date(Date.now() + request.expires_in * 1e3).toISOString(),
906
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
907
+ };
908
+ }
909
+ function remainingDeviceRequest(pending) {
910
+ return {
911
+ device_code: pending.device_code,
912
+ user_code: pending.user_code,
913
+ verification_uri: pending.verification_uri,
914
+ verification_uri_complete: pending.verification_uri_complete,
915
+ interval: pending.interval,
916
+ expires_in: Math.max(0, Math.floor((Date.parse(pending.expires_at) - Date.now()) / 1e3))
917
+ };
918
+ }
919
+ async function announceDeviceCode(verification, userCode, opts, json2) {
920
+ process.stderr.write(redactText(`\uC571 \uB9C8\uC774\uD398\uC774\uC9C0 \u2192 \uACC4\uC815 \u2192 \uB0B4 \uC815\uBCF4 \uC218\uC815\uD558\uAE30 \u2192 \uC678\uBD80 \uB3C4\uAD6C \uC5F0\uACB0 \u2192 \uCF54\uB4DC\uB85C \uC2B9\uC778\uC5D0\uC11C ${userCode} \uC785\uB825
921
+ ${verification.href}
922
+ `));
923
+ if (opts.qr !== false && process.stderr.isTTY && !json2) qr.generate(verification.href, { small: true }, (value2) => process.stderr.write(value2 + "\n"));
924
+ if (opts.browser && process.stdin.isTTY && !json2) await open2(verification.href).catch(() => process.stderr.write("\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC704 \uC8FC\uC18C\uB098 \uC571\uC5D0\uC11C \uC2B9\uC778\uD558\uC138\uC694.\n"));
925
+ }
849
926
  async function tokenInput(option) {
850
927
  if (typeof option === "string") return validateToken(option);
851
928
  if (process.env.CLACK_TOKEN) return validateToken(process.env.CLACK_TOKEN);
@@ -858,11 +935,12 @@ async function tokenInput(option) {
858
935
  return validateToken(value2);
859
936
  }
860
937
  function registerAuthCommands(program2, runtime2, store2) {
861
- runtime2.action(program2.command("login").description("\uC571\uC5D0\uC11C \uC2B9\uC778\uD558\uAC70\uB098 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC73C\uB85C \uC5F0\uACB0").option("--token [pat]", "\uD1A0\uD070 \uC785\uB825. \uAC12\uC744 \uC0DD\uB7B5\uD558\uBA74 \uC228\uAE40 \uD504\uB86C\uD504\uD2B8 \uB610\uB294 \uD45C\uC900 \uC785\uB825 \uC0AC\uC6A9").option("--scopes <scopes>", "\uC694\uCCAD \uAD8C\uD55C (\uC27C\uD45C \uAD6C\uBD84)", "profile:read").option("--no-browser", "\uBE0C\uB77C\uC6B0\uC800 \uC790\uB3D9 \uC5F4\uAE30 \uC0DD\uB7B5").option("--no-qr", "QR \uCF54\uB4DC \uCD9C\uB825 \uC0DD\uB7B5 (\uC5D0\uC774\uC804\uD2B8\xB7\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0 \uC801\uD569)").option("--label <name>", "\uAE30\uAE30 \uC774\uB984", hostname()), async (ctx, _args, opts) => {
938
+ runtime2.action(program2.command("login").description("\uC571\uC5D0\uC11C \uC2B9\uC778\uD558\uAC70\uB098 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC73C\uB85C \uC5F0\uACB0").option("--token [pat]", "\uD1A0\uD070 \uC785\uB825. \uAC12\uC744 \uC0DD\uB7B5\uD558\uBA74 \uC228\uAE40 \uD504\uB86C\uD504\uD2B8 \uB610\uB294 \uD45C\uC900 \uC785\uB825 \uC0AC\uC6A9").option("--scopes <scopes>", "\uC694\uCCAD \uAD8C\uD55C (\uC27C\uD45C \uAD6C\uBD84). product \uAC19\uC740 \uBB36\uC74C \uC774\uB984\uC740 :read\xB7:write\uB9CC \uC694\uCCAD\uD558\uBA70, \uC81C\uCD9C\xB7\uAC8C\uC2DC\uC5D0 \uD544\uC694\uD55C *:publish\uB294 \uB530\uB85C \uC801\uB294\uB2E4", "profile:read").option("--no-browser", "\uBE0C\uB77C\uC6B0\uC800 \uC790\uB3D9 \uC5F4\uAE30 \uC0DD\uB7B5").option("--no-qr", "QR \uCF54\uB4DC \uCD9C\uB825 \uC0DD\uB7B5 (\uC5D0\uC774\uC804\uD2B8\xB7\uBE44\uB300\uD654\uD615 \uD658\uACBD\uC5D0 \uC801\uD569)").option("--label <name>", "\uAE30\uAE30 \uC774\uB984", hostname()).option("--no-wait", "\uCF54\uB4DC\uB9CC \uBC1C\uAE09\uD558\uACE0 \uC2B9\uC778\uC744 \uAE30\uB2E4\uB9AC\uC9C0 \uC54A\uC74C (\uAC19\uC740 \uD134\uC744 \uB05D\uB0B4\uC57C \uD558\uB294 \uC5D0\uC774\uC804\uD2B8\uC5D0 \uC801\uD569, \uC774\uD6C4 clack login --resume\uC73C\uB85C \uC774\uC5B4\uBC1B\uC74C)").option("--resume", "\uC774\uC804\uC5D0 \uBC1C\uAE09\uD588\uC9C0\uB9CC \uC544\uC9C1 \uB9CC\uB8CC\uB418\uC9C0 \uC54A\uC740 \uC694\uCCAD\uC744 \uC774\uC5B4\uC11C \uAE30\uB2E4\uB9BC (\uC0C8 \uCF54\uB4DC\uB97C \uBC1C\uAE09\uD558\uC9C0 \uC54A\uC74C)"), async (ctx, _args, opts) => {
862
939
  if (ctx.options.dryRun) throw new CliError("login\uC740 --dry-run\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
863
940
  const resolved = await store2.resolve(ctx.options, true);
864
941
  let credential;
865
942
  if (opts.token !== void 0 || process.env.CLACK_TOKEN) {
943
+ if (opts.resume) throw new CliError("--resume\uC740 --token\uACFC \uD568\uAED8 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
866
944
  const token = await tokenInput(opts.token);
867
945
  const api = new ApiClient({ baseUrl: resolved.baseUrl, token, verbose: ctx.options.verbose });
868
946
  const row = await currentToken(api, token);
@@ -873,36 +951,77 @@ function registerAuthCommands(program2, runtime2, store2) {
873
951
  credential.user = { id: result.data.id, name: result.data.name, avatar: result.data.avatar };
874
952
  }
875
953
  } else {
876
- const scopes = parseScopes(opts.scopes);
877
- if (typeof opts.label !== "string" || !opts.label.trim() || opts.label.length > 120) throw new CliError("\uAE30\uAE30 \uC774\uB984\uC740 1~120\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
878
- const { data: request } = await ctx.api.request("POST", "/v4/auth/device/code", { auth: false, body: {
879
- client_name: "cli",
880
- client_version: VERSION,
881
- device_name: opts.label,
882
- scopes
883
- } });
884
- rememberSecret(request.device_code);
885
- if (!/^dvc_[a-f0-9]{64}$/.test(request.device_code) || !/^[A-Z2-9]{4}-[A-Z2-9]{4}$/.test(request.user_code) || !Number.isFinite(request.expires_in) || request.expires_in <= 0 || request.expires_in > 600 || !Number.isFinite(request.interval) || request.interval < 1) throw new CliError("\uC2B9\uC778 \uC694\uCCAD \uC751\uB2F5\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 0);
886
- const verification = new URL(request.verification_uri_complete);
887
- if (verification.protocol !== "https:" || !["clack.kr", "dev.clack.kr", "www.clack.kr"].includes(verification.hostname) || verification.username || verification.password || verification.port) throw new CliError("\uC2B9\uC778 \uD398\uC774\uC9C0 \uC8FC\uC18C\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 0);
888
- process.stderr.write(redactText(`\uC571 \uB9C8\uC774\uD398\uC774\uC9C0 \u2192 \uACC4\uC815 \u2192 \uB0B4 \uC815\uBCF4 \uC218\uC815\uD558\uAE30 \u2192 \uC678\uBD80 \uB3C4\uAD6C \uC5F0\uACB0 \u2192 \uCF54\uB4DC\uB85C \uC2B9\uC778\uC5D0\uC11C ${request.user_code} \uC785\uB825
889
- ${verification.href}
890
- `));
891
- if (opts.qr !== false && process.stderr.isTTY && !ctx.options.json) qr.generate(verification.href, { small: true }, (value2) => process.stderr.write(value2 + "\n"));
892
- if (opts.browser && process.stdin.isTTY && !ctx.options.json) await open2(verification.href).catch(() => process.stderr.write("\uBE0C\uB77C\uC6B0\uC800\uB97C \uC5F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC704 \uC8FC\uC18C\uB098 \uC571\uC5D0\uC11C \uC2B9\uC778\uD558\uC138\uC694.\n"));
893
- const grant = await pollDevice(ctx.api, request);
954
+ let pending;
955
+ if (opts.resume) {
956
+ const stored = await store2.getPendingDeviceRequest(resolved.profile);
957
+ if (!stored) throw new CliError("\uB300\uAE30 \uC911\uC778 \uB85C\uADF8\uC778 \uC694\uCCAD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. clack login\uC73C\uB85C \uC0C8\uB85C \uC2DC\uC791\uD558\uC138\uC694.", "DEVICE_REQUEST_NOT_FOUND", 404);
958
+ if ((ctx.options.env || ctx.options.baseUrl) && resolved.baseUrl !== stored.base_url) {
959
+ throw new CliError(`\uB300\uAE30 \uC911\uC778 \uC694\uCCAD\uC740 ${stored.base_url}\uC5D0 \uBC1C\uAE09\uB418\uC5C8\uC2B5\uB2C8\uB2E4. --env\xB7--base-url \uC5C6\uC774 clack login --resume\uC744 \uC2E4\uD589\uD558\uC138\uC694.`, "DEVICE_REQUEST_ORIGIN_MISMATCH", 401);
960
+ }
961
+ pending = stored;
962
+ } else {
963
+ const scopes = parseScopes(opts.scopes);
964
+ if (typeof opts.label !== "string" || !opts.label.trim() || opts.label.length > 120) throw new CliError("\uAE30\uAE30 \uC774\uB984\uC740 1~120\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
965
+ const { data: request } = await ctx.api.request("POST", "/v4/auth/device/code", { auth: false, body: {
966
+ client_name: "cli",
967
+ client_version: VERSION,
968
+ device_name: opts.label,
969
+ scopes
970
+ } });
971
+ rememberSecret(request.device_code);
972
+ validateFreshDeviceRequest(request);
973
+ pending = newPendingRequest(request, scopes, resolved.baseUrl);
974
+ await store2.savePendingDeviceRequest(resolved.profile, pending);
975
+ }
976
+ rememberSecret(pending.device_code);
977
+ const verification = validatePendingRequest(pending);
978
+ await announceDeviceCode(verification, pending.user_code, opts, Boolean(ctx.options.json));
979
+ if (opts.wait === false) {
980
+ const expiry = formatInstant(pending.expires_at, "utc-v1", ctx.options.time ?? "local");
981
+ if (ctx.options.json) {
982
+ ctx.output({
983
+ status: "pending",
984
+ user_code: pending.user_code,
985
+ verification_uri: pending.verification_uri,
986
+ verification_uri_complete: pending.verification_uri_complete,
987
+ expires_at: pending.expires_at,
988
+ interval: pending.interval,
989
+ message: "\uC571\uC5D0\uC11C \uC2B9\uC778\uD55C \uB4A4 \uC774\uC5B4\uC11C \uBC1B\uC73C\uB824\uBA74 clack login --resume\uC744 \uC2E4\uD589\uD558\uC138\uC694."
990
+ });
991
+ } else {
992
+ process.stdout.write(redactText([
993
+ `\uC2B9\uC778\uC744 \uAE30\uB2E4\uB9AC\uC9C0 \uC54A\uACE0 \uBC18\uD658\uD569\uB2C8\uB2E4. (\uD504\uB85C\uD544: ${resolved.profile}, \uC8FC\uC18C: ${pending.base_url})`,
994
+ `\uCF54\uB4DC\uB294 ${expiry}\uAE4C\uC9C0 \uC720\uD6A8\uD569\uB2C8\uB2E4.`,
995
+ "\uC571\uC5D0\uC11C \uC2B9\uC778\uD55C \uB4A4 \uC774\uC5B4\uC11C \uBC1B\uC73C\uB824\uBA74: clack login --resume"
996
+ ].join("\n") + "\n"));
997
+ }
998
+ return;
999
+ }
1000
+ const api = opts.resume ? new ApiClient({ baseUrl: pending.base_url, verbose: ctx.options.verbose }) : ctx.api;
1001
+ let grant;
1002
+ try {
1003
+ grant = await pollDevice(api, remainingDeviceRequest(pending));
1004
+ } catch (error2) {
1005
+ if (error2 instanceof CliError && TERMINAL_DEVICE_FAILURE_CODES.includes(error2.code)) {
1006
+ await store2.removePendingDeviceRequest(resolved.profile).catch(() => {
1007
+ });
1008
+ }
1009
+ throw error2;
1010
+ }
894
1011
  const token = validateToken(grant.token);
895
- if (!Array.isArray(grant.scopes) || grant.scopes.some((scope) => !scopes.includes(scope)) || !Number.isFinite(Date.parse(grant.expires_at)) || !grant.user || !Number.isSafeInteger(grant.user.id)) throw new CliError("\uAD50\uBD80 \uC751\uB2F5\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC571\uC5D0\uC11C \uC5F0\uACB0 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694.", "INVALID_RESPONSE", 0);
896
- credential = { token, base_url: resolved.baseUrl, scopes: grant.scopes, expires_at: grant.expires_at, user: grant.user };
1012
+ if (!Array.isArray(grant.scopes) || grant.scopes.some((scope) => !pending.scopes.includes(scope)) || !Number.isFinite(Date.parse(grant.expires_at)) || !grant.user || !Number.isSafeInteger(grant.user.id)) throw new CliError("\uAD50\uBD80 \uC751\uB2F5\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC571\uC5D0\uC11C \uC5F0\uACB0 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694.", "INVALID_RESPONSE", 0);
1013
+ credential = { token, base_url: pending.base_url, scopes: grant.scopes, expires_at: grant.expires_at, user: grant.user };
1014
+ await store2.removePendingDeviceRequest(resolved.profile).catch(() => {
1015
+ });
897
1016
  }
898
1017
  await store2.saveCredential(resolved.profile, credential);
899
- await store2.setConfig(resolved.profile, "base_url", resolved.baseUrl);
1018
+ await store2.setConfig(resolved.profile, "base_url", credential.base_url);
900
1019
  if (ctx.options.json) {
901
1020
  ctx.output({ profile: resolved.profile, user: credential.user ?? null, scopes: credential.scopes, expires_at: credential.expires_at, message: "\uC5F0\uACB0\uB418\uC5C8\uC2B5\uB2C8\uB2E4." });
902
1021
  } else {
903
1022
  const expiry = formatInstant(credential.expires_at, "utc-v1", ctx.options.time ?? "local");
904
1023
  process.stdout.write(redactText([
905
- `\uC5F0\uACB0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (\uD504\uB85C\uD544: ${resolved.profile}, \uC8FC\uC18C: ${resolved.baseUrl})`,
1024
+ `\uC5F0\uACB0\uB418\uC5C8\uC2B5\uB2C8\uB2E4. (\uD504\uB85C\uD544: ${resolved.profile}, \uC8FC\uC18C: ${credential.base_url})`,
906
1025
  credential.user ? `\uC0AC\uC6A9\uC790: ${credential.user.name ?? "\uC774\uB984 \uC5C6\uC74C"} (#${credential.user.id})` : "\uC0AC\uC6A9\uC790: profile:read \uAD8C\uD55C\uC774 \uC5C6\uC5B4 \uC870\uD68C\uD558\uC9C0 \uC54A\uC74C",
907
1026
  `\uAD8C\uD55C: ${credential.scopes.join(", ")}`,
908
1027
  `\uB9CC\uB8CC: ${expiry}`
@@ -1143,9 +1262,9 @@ function isRemoteImage(value2) {
1143
1262
  return false;
1144
1263
  }
1145
1264
  }
1146
- function dimensions(bytes) {
1265
+ function dimensions(bytes2) {
1147
1266
  try {
1148
- const result = imageSize(bytes);
1267
+ const result = imageSize(bytes2);
1149
1268
  if (!result.width || !result.height || result.width > 2e4 || result.height > 2e4 || !result.type || !MIME[result.type]) throw new Error();
1150
1269
  return { width: result.width, height: result.height, mime: MIME[result.type] };
1151
1270
  } catch {
@@ -1172,26 +1291,26 @@ async function prepareImage(source, baseDir = process.cwd(), measureRemote = fal
1172
1291
  } finally {
1173
1292
  await reader.cancel();
1174
1293
  }
1175
- const bytes2 = new Uint8Array(size);
1294
+ const bytes3 = new Uint8Array(size);
1176
1295
  let offset = 0;
1177
1296
  for (const chunk of chunks) {
1178
- bytes2.set(chunk, offset);
1297
+ bytes3.set(chunk, offset);
1179
1298
  offset += chunk.length;
1180
1299
  }
1181
- return { source, ...dimensions(bytes2) };
1300
+ return { source, ...dimensions(bytes3) };
1182
1301
  }
1183
1302
  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.");
1184
1303
  const path = resolve(baseDir, source);
1185
- let bytes;
1304
+ let bytes2;
1186
1305
  try {
1187
1306
  const info = await stat(path);
1188
1307
  if (!info.isFile() || info.size === 0 || info.size > MAX_IMAGE_BYTES) throw new Error();
1189
- bytes = await readFile5(path);
1190
- if (bytes.length > MAX_IMAGE_BYTES) throw new Error();
1308
+ bytes2 = await readFile5(path);
1309
+ if (bytes2.length > MAX_IMAGE_BYTES) throw new Error();
1191
1310
  } catch {
1192
1311
  throw new CliError(`10MB \uC774\uD558 \uC774\uBBF8\uC9C0 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path}`);
1193
1312
  }
1194
- return { source: path, bytes, ...dimensions(bytes) };
1313
+ return { source: path, bytes: bytes2, ...dimensions(bytes2) };
1195
1314
  }
1196
1315
  async function uploadPrepared(ctx, image2) {
1197
1316
  if (!image2.bytes) return { originalUrl: image2.source };
@@ -1879,6 +1998,11 @@ import { createInterface } from "readline";
1879
1998
  import { z as z10 } from "zod";
1880
1999
 
1881
2000
  // src/commands/skill.ts
2001
+ import { spawn } from "child_process";
2002
+ import { existsSync } from "fs";
2003
+ import { writeFile as writeFile2 } from "fs/promises";
2004
+ import { createRequire } from "module";
2005
+ import { join as join3, resolve as resolve8 } from "path";
1882
2006
  import { z as z9 } from "zod";
1883
2007
 
1884
2008
  // src/commands/content.ts
@@ -1915,15 +2039,15 @@ var CAPABILITIES = /* @__PURE__ */ new Set([
1915
2039
  function isObject(value2) {
1916
2040
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
1917
2041
  }
1918
- function parseObject(bytes) {
2042
+ function parseObject(bytes2) {
1919
2043
  try {
1920
- const value2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
2044
+ const value2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
1921
2045
  return isObject(value2) ? value2 : null;
1922
2046
  } catch {
1923
2047
  return null;
1924
2048
  }
1925
2049
  }
1926
- function inspectContentPackage(bytes) {
2050
+ function inspectContentPackage(bytes2) {
1927
2051
  const warnings = [];
1928
2052
  const paths = /* @__PURE__ */ new Set();
1929
2053
  const profileFiles = /* @__PURE__ */ new Set();
@@ -1942,7 +2066,7 @@ function inspectContentPackage(bytes) {
1942
2066
  let tooLargeExpanded = false;
1943
2067
  let files;
1944
2068
  try {
1945
- files = unzipSync(bytes, { filter(entry) {
2069
+ files = unzipSync(bytes2, { filter(entry) {
1946
2070
  entries++;
1947
2071
  if (entries > MAX_FILES) {
1948
2072
  tooManyFiles = true;
@@ -2029,27 +2153,27 @@ async function prepareContentFile(source) {
2029
2153
  const file = resolve6(source);
2030
2154
  const extension = extname(file).toLowerCase();
2031
2155
  if (![".html", ".htm", ".zip"].includes(extension)) throw new CliError("HTML \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.");
2032
- let bytes;
2156
+ let bytes2;
2033
2157
  try {
2034
2158
  const info = await stat2(file);
2035
2159
  if (!info.isFile() || info.size === 0 || info.size > MAX_BYTES) throw new Error();
2036
- bytes = await readFile8(file);
2037
- if (!bytes.length || bytes.length > MAX_BYTES) throw new Error();
2160
+ bytes2 = await readFile8(file);
2161
+ if (!bytes2.length || bytes2.length > MAX_BYTES) throw new Error();
2038
2162
  } catch {
2039
2163
  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.");
2040
2164
  }
2041
2165
  return {
2042
- bytes,
2043
- warnings: extension === ".zip" ? inspectContentPackage(bytes) : [],
2166
+ bytes: bytes2,
2167
+ warnings: extension === ".zip" ? inspectContentPackage(bytes2) : [],
2044
2168
  metadata: {
2045
2169
  filename: basename3(file),
2046
2170
  content_type: extension === ".zip" ? "application/zip" : "text/html",
2047
- byte_size: bytes.length,
2048
- sha256: createHash2("sha256").update(bytes).digest("hex")
2171
+ byte_size: bytes2.length,
2172
+ sha256: createHash2("sha256").update(bytes2).digest("hex")
2049
2173
  }
2050
2174
  };
2051
2175
  }
2052
- async function putContentFile(session2, bytes, fetcher = fetch) {
2176
+ async function putContentFile(session2, bytes2, fetcher = fetch) {
2053
2177
  uuid(session2.upload_id);
2054
2178
  let url;
2055
2179
  try {
@@ -2060,7 +2184,7 @@ async function putContentFile(session2, bytes, fetcher = fetch) {
2060
2184
  }
2061
2185
  const contentType = session2.headers?.["Content-Type"];
2062
2186
  const checksum = session2.headers?.["x-amz-checksum-sha256"];
2063
- if (!["text/html", "application/zip"].includes(contentType ?? "") || checksum !== createHash2("sha256").update(bytes).digest("base64")) {
2187
+ if (!["text/html", "application/zip"].includes(contentType ?? "") || checksum !== createHash2("sha256").update(bytes2).digest("base64")) {
2064
2188
  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);
2065
2189
  }
2066
2190
  rememberSecret(session2.upload_url);
@@ -2068,7 +2192,7 @@ async function putContentFile(session2, bytes, fetcher = fetch) {
2068
2192
  const result = await fetcher(url, {
2069
2193
  method: "PUT",
2070
2194
  headers: { "Content-Type": contentType, "x-amz-checksum-sha256": checksum },
2071
- body: new Uint8Array(bytes),
2195
+ body: new Uint8Array(bytes2),
2072
2196
  redirect: "error",
2073
2197
  signal: AbortSignal.timeout(12e4)
2074
2198
  });
@@ -2086,15 +2210,15 @@ var keyId = (value2) => {
2086
2210
  return id2;
2087
2211
  };
2088
2212
  function registerContentCommands(program2, runtime2) {
2089
- const content = program2.command("content").description("HTML \uCF58\uD150\uCE20 \uB4F1\uB85D\xB7\uC571 \uD655\uC778\xB7\uC2EC\uC0AC \uAD00\uB9AC");
2090
- runtime2.action(content.command("config"), async (ctx) => ctx.output(await ctx.api.request("GET", "/v4/creator/config", { auth: false })));
2091
- runtime2.action(content.command("list"), async (ctx) => ctx.output(await ctx.api.request("GET", "/v4/creator/contents")));
2092
- runtime2.action(content.command("status <id>"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}`)));
2093
- runtime2.action(content.command("create").requiredOption("--title <text>", "\uC81C\uBAA9").option("--description <text>", "\uC124\uBA85", "").option("--kind <kind>", "html|gallery|slideshow|video", "html").requiredOption("--policy-version <version>", "\uB3D9\uC758\uD55C \uCF58\uD150\uCE20 \uC81C\uACF5 \uC815\uCC45 \uBC84\uC804"), async (ctx, _args, opts) => {
2213
+ const content = program2.command("content").description("HTML \uCF58\uD150\uCE20 \uB4F1\uB85D\xB7\uC571 \uD655\uC778\xB7\uC2EC\uC0AC \uAD00\uB9AC (creator-content:read\xB7write\xB7publish)");
2214
+ runtime2.action(content.command("config").description("\uCF58\uD150\uCE20 \uAE30\uB2A5 \uC81C\uACF5 \uC5EC\uBD80\xB7\uC124\uC815 \uC870\uD68C (\uB85C\uADF8\uC778 \uBD88\uD544\uC694)"), async (ctx) => ctx.output(await ctx.api.request("GET", "/v4/creator/config", { auth: false })));
2215
+ runtime2.action(content.command("list").description("\uB0B4 \uCF58\uD150\uCE20 \uBAA9\uB85D (creator-content:read)"), async (ctx) => ctx.output(await ctx.api.request("GET", "/v4/creator/contents")));
2216
+ runtime2.action(content.command("status <id>").description("\uCF58\uD150\uCE20\uC640 \uBC84\uC804\uBCC4 \uC5C5\uB85C\uB4DC\xB7\uC2EC\uC0AC\xB7\uACF5\uAC1C \uC0C1\uD0DC (creator-content:read)"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}`)));
2217
+ runtime2.action(content.command("create").description("\uCF58\uD150\uCE20 \uCD08\uC548 \uB4F1\uB85D (creator-content:write)").requiredOption("--title <text>", "\uC81C\uBAA9").option("--description <text>", "\uC124\uBA85", "").option("--kind <kind>", "html|gallery|slideshow|video", "html").requiredOption("--policy-version <version>", "\uB3D9\uC758\uD55C \uCF58\uD150\uCE20 \uC81C\uACF5 \uC815\uCC45 \uBC84\uC804"), async (ctx, _args, opts) => {
2094
2218
  const body = parse(createSchema, { title: opts.title, description: opts.description, kind: opts.kind, policy_version: opts.policyVersion });
2095
2219
  await mutate(ctx, "POST", "/v4/creator/contents", body);
2096
2220
  });
2097
- runtime2.action(content.command("upload <id> <file>").addOption(new Option("--header <mode>", "\uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"])).addOption(new Option("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"])), async (ctx, [id2, file], opts) => {
2221
+ 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) => {
2098
2222
  const path = `/v4/creator/contents/${uuid(id2)}`;
2099
2223
  const prepared = await prepareContentFile(file);
2100
2224
  for (const warning of prepared.warnings) process.stderr.write(`\uD328\uD0A4\uC9C0 \uACBD\uACE0: ${warning}
@@ -2110,39 +2234,39 @@ function registerContentCommands(program2, runtime2) {
2110
2234
  throw error2;
2111
2235
  }
2112
2236
  });
2113
- runtime2.action(content.command("complete <id> <upload-id>"), async (ctx, [id2, uploadId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/uploads/${uuid(uploadId)}/complete`));
2114
- runtime2.action(content.command("preview <id> <version-id>"), async (ctx, [id2, versionId]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/preview`)));
2115
- runtime2.action(content.command("submit <id> <version-id>"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/submit`, { publish_on_approval: true }, "\uC774 \uBC84\uC804\uC744 \uC2EC\uC0AC\uD558\uACE0 \uD1B5\uACFC\uD558\uBA74 \uACF5\uAC1C\uD560\uAE4C\uC694?"));
2116
- runtime2.action(content.command("withdraw <id> <version-id>"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/review/cancel`, void 0, "\uC774 \uBC84\uC804\uC758 \uC2EC\uC0AC\uB97C \uCDE8\uC18C\uD560\uAE4C\uC694?"));
2117
- runtime2.action(content.command("publish <id> <version-id>"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/versions/${uuid(versionId)}/publish`, void 0, "\uC2B9\uC778\uB41C \uC774 \uBC84\uC804\uC744 \uACF5\uAC1C\uD560\uAE4C\uC694?"));
2118
- runtime2.action(content.command("unpublish <id>"), async (ctx, [id2]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/unpublish`, void 0, "\uCF58\uD150\uCE20 \uAC8C\uC2DC\uB97C \uC911\uB2E8\uD560\uAE4C\uC694?"));
2237
+ runtime2.action(content.command("complete <id> <upload-id>").description("\uC5C5\uB85C\uB4DC \uC644\uB8CC \uCC98\uB9AC \uC7AC\uC2DC\uB3C4 (creator-content:write)"), async (ctx, [id2, uploadId]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/uploads/${uuid(uploadId)}/complete`));
2238
+ 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`)));
2239
+ 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?"));
2240
+ 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?"));
2241
+ 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?"));
2242
+ runtime2.action(content.command("unpublish <id>").description("\uCF58\uD150\uCE20 \uAC8C\uC2DC \uC911\uB2E8 (creator-content:publish)"), async (ctx, [id2]) => mutate(ctx, "POST", `/v4/creator/contents/${uuid(id2)}/unpublish`, void 0, "\uCF58\uD150\uCE20 \uAC8C\uC2DC\uB97C \uC911\uB2E8\uD560\uAE4C\uC694?"));
2119
2243
  const serverKeys = content.command("server-keys").description("\uCF58\uD150\uCE20 \uC11C\uBC84 \uD0A4 \uC870\uD68C\xB7\uD3D0\uAE30 (platform:read\xB7platform:write, \uBC1C\uAE09\xB7\uD68C\uC804\uC740 \uD06C\uB9AC\uC5D0\uC774\uD130 \uC13C\uD130)");
2120
- runtime2.action(serverKeys.command("list <id>"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/server-keys`)));
2244
+ runtime2.action(serverKeys.command("list <id>").description("\uC11C\uBC84 \uD0A4 \uBAA9\uB85D (platform:read)"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/server-keys`)));
2121
2245
  for (const name2 of ["issue", "rotate"]) {
2122
2246
  runtime2.action(serverKeys.command(name2).description("\uD06C\uB9AC\uC5D0\uC774\uD130 \uC13C\uD130 \uC804\uC6A9 \uC548\uB0B4").argument("[args...]").allowUnknownOption(), async () => {
2123
2247
  throw new CliError(SERVER_KEY_CENTER_ONLY, "SERVER_KEY_CENTER_ONLY", 403);
2124
2248
  });
2125
2249
  }
2126
- runtime2.action(serverKeys.command("revoke <id> <key-id>"), async (ctx, [id2, keyIdValue]) => mutate(ctx, "DELETE", `/v4/creator/contents/${uuid(id2)}/server-keys/${keyId(keyIdValue)}`, void 0, "\uC774 \uC11C\uBC84 \uD0A4\uB97C \uC989\uC2DC \uD3D0\uAE30\uD560\uAE4C\uC694?"));
2250
+ runtime2.action(serverKeys.command("revoke <id> <key-id>").description("\uC11C\uBC84 \uD0A4 \uC989\uC2DC \uD3D0\uAE30 (platform:write)"), async (ctx, [id2, keyIdValue]) => mutate(ctx, "DELETE", `/v4/creator/contents/${uuid(id2)}/server-keys/${keyId(keyIdValue)}`, void 0, "\uC774 \uC11C\uBC84 \uD0A4\uB97C \uC989\uC2DC \uD3D0\uAE30\uD560\uAE4C\uC694?"));
2127
2251
  const shared = content.command("shared").description("\uACF5\uC720 \uBB38\uC11C \uC870\uD68C\xB7\uC228\uAE40\xB7\uC0AD\uC81C (platform:read\xB7platform:write)");
2128
- runtime2.action(shared.command("collections <id>"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/shared-document-collections`)));
2129
- runtime2.action(shared.command("list <id> <collection>").option("--limit <count>", "1~100\uAC1C, \uAE30\uBCF8 50"), async (ctx, [id2, collectionValue], opts) => ctx.output(await ctx.api.request(
2252
+ runtime2.action(shared.command("collections <id>").description("\uACF5\uC720 \uBB38\uC11C \uCEEC\uB809\uC158 \uC774\uB984 (platform:read)"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `/v4/creator/contents/${uuid(id2)}/shared-document-collections`)));
2253
+ runtime2.action(shared.command("list <id> <collection>").description("\uACF5\uC720 \uBB38\uC11C \uBAA9\uB85D (platform:read)").option("--limit <count>", "1~100\uAC1C, \uAE30\uBCF8 50"), async (ctx, [id2, collectionValue], opts) => ctx.output(await ctx.api.request(
2130
2254
  "GET",
2131
2255
  `/v4/creator/contents/${uuid(id2)}/shared-documents/${parse(collection, collectionValue)}`,
2132
2256
  { query: { limit: opts.limit } }
2133
2257
  )));
2134
- runtime2.action(shared.command("get <id> <collection> <key>"), async (ctx, [id2, collectionValue, keyValue]) => ctx.output(await ctx.api.request(
2258
+ runtime2.action(shared.command("get <id> <collection> <key>").description("\uACF5\uC720 \uBB38\uC11C \uD558\uB098 \uC870\uD68C (platform:read)"), async (ctx, [id2, collectionValue, keyValue]) => ctx.output(await ctx.api.request(
2135
2259
  "GET",
2136
2260
  `/v4/creator/contents/${uuid(id2)}/shared-documents/${parse(collection, collectionValue)}/${parse(documentKey, keyValue)}`
2137
2261
  )));
2138
- runtime2.action(shared.command("hide <id> <collection> <key>"), async (ctx, [id2, collectionValue, keyValue]) => mutate(
2262
+ runtime2.action(shared.command("hide <id> <collection> <key>").description("\uACF5\uC720 \uBB38\uC11C \uC228\uAE40 (platform:write)"), async (ctx, [id2, collectionValue, keyValue]) => mutate(
2139
2263
  ctx,
2140
2264
  "POST",
2141
2265
  `/v4/creator/contents/${uuid(id2)}/shared-documents/${parse(collection, collectionValue)}/${parse(documentKey, keyValue)}/hide`,
2142
2266
  void 0,
2143
2267
  "\uC774 \uACF5\uC720 \uBB38\uC11C\uB97C \uC228\uAE38\uAE4C\uC694? \uBAA9\uB85D\xB7\uC21C\uC704\uC5D0\uC11C \uC81C\uC678\uB418\uBA70 \uBB38\uC11C \uC790\uCCB4\uB294 \uB0A8\uC2B5\uB2C8\uB2E4."
2144
2268
  ));
2145
- runtime2.action(shared.command("delete <id> <collection> <key>"), async (ctx, [id2, collectionValue, keyValue]) => mutate(
2269
+ runtime2.action(shared.command("delete <id> <collection> <key>").description("\uACF5\uC720 \uBB38\uC11C \uC601\uAD6C \uC0AD\uC81C (platform:write)"), async (ctx, [id2, collectionValue, keyValue]) => mutate(
2146
2270
  ctx,
2147
2271
  "DELETE",
2148
2272
  `/v4/creator/contents/${uuid(id2)}/shared-documents/${parse(collection, collectionValue)}/${parse(documentKey, keyValue)}`,
@@ -2152,36 +2276,155 @@ function registerContentCommands(program2, runtime2) {
2152
2276
  }
2153
2277
 
2154
2278
  // src/vendor/clack-api-v4-contracts/authoring-semantics.mjs
2155
- function inspectAuthoringSemantics(skill, form) {
2279
+ function walkDataSchema(schema, visit) {
2280
+ const defs = schema?.$defs ?? {};
2281
+ const step = (node, info) => {
2282
+ if (!node || typeof node !== "object" || Array.isArray(node)) return;
2283
+ if (typeof node.$ref === "string") {
2284
+ const key = node.$ref.startsWith("#/$defs/") ? node.$ref.slice("#/$defs/".length) : null;
2285
+ if (key === null || !Object.hasOwn(defs, key) || info.refs.includes(key)) {
2286
+ visit(node, { ...info, cycle: key !== null && info.refs.includes(key), unresolved: key === null || !Object.hasOwn(defs, key) });
2287
+ return;
2288
+ }
2289
+ visit(node, info);
2290
+ step(defs[key], { ...info, refs: [...info.refs, key] });
2291
+ return;
2292
+ }
2293
+ visit(node, info);
2294
+ for (const branch of Array.isArray(node.oneOf) ? node.oneOf : []) step(branch, { ...info, branch: true });
2295
+ for (const [name2, child] of Object.entries(node.properties && typeof node.properties === "object" ? node.properties : {})) {
2296
+ step(child, {
2297
+ ...info,
2298
+ pointer: `${info.pointer}/${name2}`,
2299
+ depth: info.depth + 1,
2300
+ root: info.depth === 0 ? name2 : info.root,
2301
+ visibility: info.depth === 0 ? child?.["x-clack-visibility"] : info.visibility,
2302
+ branch: false
2303
+ });
2304
+ }
2305
+ if (node.items && typeof node.items === "object") step(node.items, { ...info, pointer: `${info.pointer}/*`, depth: info.depth + 1, branch: false });
2306
+ };
2307
+ step(schema, { pointer: "", depth: 0, root: null, visibility: null, refs: [], branch: false });
2308
+ }
2309
+ function dataRootField(schema, name2) {
2310
+ const field2 = schema?.properties?.[name2];
2311
+ if (!field2 || typeof field2 !== "object") return null;
2312
+ if (typeof field2.$ref === "string" && field2.$ref.startsWith("#/$defs/")) {
2313
+ const target = schema.$defs?.[field2.$ref.slice("#/$defs/".length)];
2314
+ return target && typeof target === "object" ? { ...target, "x-clack-visibility": field2["x-clack-visibility"] } : null;
2315
+ }
2316
+ return field2;
2317
+ }
2318
+ function inspectEditorSemantics(skill, data, fail2) {
2319
+ const editor = skill.authoring?.editor;
2320
+ if (!editor) return;
2321
+ const permissions = new Set(editor.permissions ?? []);
2322
+ 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.");
2323
+ 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.");
2324
+ const imageTool = (skill.authoring?.tools ?? []).find((tool) => tool.tool === "image.generate");
2325
+ 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.");
2326
+ 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.");
2327
+ if (!data) return;
2328
+ let assets = 0;
2329
+ let suggest = 0;
2330
+ walkDataSchema(data, (node) => {
2331
+ if (node["x-clack-asset"]) assets++;
2332
+ if (node["x-clack-ai"]?.suggest === true) suggest++;
2333
+ });
2334
+ 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.");
2335
+ 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.");
2336
+ 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.");
2337
+ 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.");
2338
+ const metadata = skill.output?.metadata ?? {};
2339
+ for (const key of ["title", "description"]) {
2340
+ if (metadata[key] === void 0) continue;
2341
+ const field2 = dataRootField(data, metadata[key]);
2342
+ 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.");
2343
+ }
2344
+ for (const key of ["thumbnail", "thumbnail_fallback"]) {
2345
+ if (metadata[key] === void 0) continue;
2346
+ const field2 = dataRootField(data, metadata[key]);
2347
+ 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).");
2348
+ }
2349
+ }
2350
+ var INTRO_MAX_IMAGES = 8;
2351
+ var INTRO_CREATOR_COMMENT_MAX = 500;
2352
+ var INTRO_DESCRIPTION_MAX = 5e3;
2353
+ var INTRO_TEXT_WIDGETS = /* @__PURE__ */ new Set(["text", "textarea"]);
2354
+ function introFieldLabel(name2, field2) {
2355
+ const title = typeof field2?.title === "string" ? field2.title.normalize("NFC").trim() : "";
2356
+ return title || name2;
2357
+ }
2358
+ function inspectIntroSemantics(skill, form, slots, fail2) {
2359
+ const intro = skill.output?.intro;
2360
+ if (!intro) return;
2361
+ let images = 0;
2362
+ for (const name2 of intro.images ?? []) {
2363
+ const slot = slots.find((entry) => entry.field === name2);
2364
+ if (!slot) {
2365
+ fail2("/output/intro/images", "\uC778\uD2B8\uB85C \uC774\uBBF8\uC9C0\uB294 output.asset_slots\uC758 \uD544\uB4DC\uB9CC \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
2366
+ continue;
2367
+ }
2368
+ images += slot.multiple ? slot.multiple.max : 1;
2369
+ }
2370
+ 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.`);
2371
+ if (!form) return;
2372
+ const properties = form.properties ?? {};
2373
+ const textField = (name2) => {
2374
+ const field2 = Object.hasOwn(properties, name2) ? properties[name2] : null;
2375
+ return field2 && field2.type === "string" && INTRO_TEXT_WIDGETS.has(field2["x-clack-widget"]) && Number.isSafeInteger(field2.maxLength) ? field2 : null;
2376
+ };
2377
+ for (const [key, max] of [["creator_comment", INTRO_CREATOR_COMMENT_MAX], ["description", INTRO_DESCRIPTION_MAX]]) {
2378
+ if (intro[key] === void 0) continue;
2379
+ const field2 = textField(intro[key]);
2380
+ 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.");
2381
+ 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.`);
2382
+ }
2383
+ const fallback = intro.description_fallback ?? [];
2384
+ let composed = 0;
2385
+ for (const name2 of fallback) {
2386
+ const field2 = textField(name2);
2387
+ if (!field2) {
2388
+ 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.");
2389
+ continue;
2390
+ }
2391
+ composed += introFieldLabel(name2, field2).length + 1 + field2.maxLength;
2392
+ }
2393
+ composed += Math.max(0, fallback.length - 1) * 2;
2394
+ 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.`);
2395
+ }
2396
+ function inspectAuthoringSemantics(skill, form, data) {
2156
2397
  const errors = [];
2157
- const fail = (path, message) => errors.push({ path, message });
2398
+ const fail2 = (path, message) => errors.push({ path, message });
2399
+ inspectEditorSemantics(skill, data, fail2);
2158
2400
  const tools = skill.authoring?.tools ?? [];
2159
- 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.");
2160
- 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.");
2401
+ 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.");
2402
+ 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.");
2161
2403
  const slots = skill.output?.asset_slots ?? [];
2162
2404
  const paths = /* @__PURE__ */ new Set();
2163
2405
  const fields3 = /* @__PURE__ */ new Set();
2164
2406
  for (const slot of slots) {
2165
- if (fields3.has(slot.field)) fail("/output/asset_slots", "\uC2AC\uB86F \uD544\uB4DC\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2407
+ if (fields3.has(slot.field)) fail2("/output/asset_slots", "\uC2AC\uB86F \uD544\uB4DC\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2166
2408
  fields3.add(slot.field);
2167
- 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.");
2409
+ 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.");
2168
2410
  for (let index = 0; index < (slot.multiple?.max ?? 1); index++) {
2169
2411
  const path = slot.multiple ? slot.multiple.path.replace("{n}", String(index)) : slot.path;
2170
- if (paths.has(path)) fail("/output/asset_slots", "\uC2AC\uB86F \uD30C\uC77C \uACBD\uB85C\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2412
+ if (paths.has(path)) fail2("/output/asset_slots", "\uC2AC\uB86F \uD30C\uC77C \uACBD\uB85C\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2171
2413
  paths.add(path);
2172
2414
  }
2173
2415
  }
2416
+ inspectIntroSemantics(skill, form, slots, fail2);
2174
2417
  if (!form) return errors;
2175
2418
  const properties = form.properties ?? {};
2176
2419
  const exists = (expression, path) => {
2177
2420
  const match = /^([a-z][a-z0-9_-]*)(?:\[(\d+)\])?$/.exec(expression);
2178
2421
  if (!match || !Object.hasOwn(properties, match[1])) {
2179
- fail(path, "\uC120\uC5B8\uD55C \uD3FC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2422
+ fail2(path, "\uC120\uC5B8\uD55C \uD3FC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
2180
2423
  return;
2181
2424
  }
2182
2425
  if (match[2] !== void 0) {
2183
2426
  const slot = slots.find((value2) => value2.field === match[1]);
2184
- 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.");
2427
+ 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.");
2185
2428
  }
2186
2429
  };
2187
2430
  const entity = skill.output?.entity_map;
@@ -2191,30 +2434,539 @@ function inspectAuthoringSemantics(skill, form) {
2191
2434
  }
2192
2435
  for (const slot of slots) {
2193
2436
  const node = properties[slot.field];
2194
- 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.");
2437
+ 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.");
2195
2438
  }
2196
2439
  for (const [name2, node] of Object.entries(properties)) {
2197
2440
  if (node["x-clack-widget"] === "content_ref") {
2198
- 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.");
2441
+ 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.");
2199
2442
  for (const target of Object.keys(node["x-clack-source"]?.import ?? {})) {
2200
2443
  exists(target, `/properties/${name2}/x-clack-source/import`);
2201
- 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.");
2444
+ 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.");
2202
2445
  }
2203
- } else if (node["x-clack-source"]) fail(`/properties/${name2}`, "x-clack-source\uB294 content_ref\uC5D0\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4.");
2204
- 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.");
2446
+ } else if (node["x-clack-source"]) fail2(`/properties/${name2}`, "x-clack-source\uB294 content_ref\uC5D0\uB9CC \uC120\uC5B8\uD569\uB2C8\uB2E4.");
2447
+ 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.");
2205
2448
  }
2206
2449
  return errors;
2207
2450
  }
2208
2451
 
2209
2452
  // src/lib/skill-package.ts
2210
- import { createHash as createHash3 } from "crypto";
2453
+ import { createHash as createHash4 } from "crypto";
2211
2454
  import { lstat as lstat4, open as open3, readdir } from "fs/promises";
2212
2455
  import { constants } from "fs";
2213
2456
  import { basename as basename4, extname as extname2, join as join2, resolve as resolve7 } from "path";
2214
- import { Ajv2020 } from "ajv/dist/2020.js";
2457
+ import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
2215
2458
  import { unzipSync as unzipSync2, zipSync } from "fflate";
2216
2459
  import { parseDocument } from "yaml";
2217
2460
 
2461
+ // src/lib/skill-editor.ts
2462
+ import { createHash as createHash3 } from "crypto";
2463
+ import { Ajv2020 } from "ajv/dist/2020.js";
2464
+
2465
+ // src/vendor/clack-api-v4-contracts/output-data-inspect.mjs
2466
+ var bytes = (value2) => Buffer.byteLength(JSON.stringify(value2), "utf8");
2467
+ var fail = (path, message) => ({ path, message });
2468
+ var OUTPUT_DATA_LIMITS = { schema_bytes: 65536, depth: 6, properties: 400, public_bytes: 262144, private_bytes: 65536, ui_state_bytes: 262144 };
2469
+ var SCALAR_TYPES = /* @__PURE__ */ new Set(["string", "integer", "number", "boolean"]);
2470
+ var SCALAR_KEYS = {
2471
+ string: ["minLength", "maxLength", "format"],
2472
+ number: ["minimum", "maximum"],
2473
+ integer: ["minimum", "maximum"],
2474
+ array: ["items", "minItems", "maxItems", "uniqueItems"],
2475
+ object: ["properties", "required", "additionalProperties"],
2476
+ boolean: []
2477
+ };
2478
+ var TYPED_KEYS = new Set(Object.values(SCALAR_KEYS).flat());
2479
+ function inspectOutputData(schema) {
2480
+ const errors = [];
2481
+ 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."));
2482
+ 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.`));
2483
+ 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."));
2484
+ const defs = schema.$defs ?? {};
2485
+ const resolve9 = (node) => typeof node?.$ref === "string" ? defs[node.$ref.slice("#/$defs/".length)] : node;
2486
+ let properties = 0;
2487
+ const suggestRoots = [];
2488
+ const assetPointers = [];
2489
+ walkDataSchema(schema, (node, info) => {
2490
+ const at = info.pointer || "/";
2491
+ if (info.cycle) {
2492
+ errors.push(fail(at, "\uC21C\uD658 \uCC38\uC870\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."));
2493
+ return;
2494
+ }
2495
+ if (info.unresolved) {
2496
+ errors.push(fail(at, "\uB85C\uCEEC $defs \uCC38\uC870\uB9CC \uD5C8\uC6A9\uD569\uB2C8\uB2E4."));
2497
+ return;
2498
+ }
2499
+ 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."));
2500
+ if (info.depth === 0) return;
2501
+ if (typeof node.$ref === "string") {
2502
+ 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."));
2503
+ return;
2504
+ }
2505
+ if (!info.branch) properties++;
2506
+ if (Array.isArray(node.oneOf)) {
2507
+ const discriminator = node["x-clack-discriminator"];
2508
+ if (!discriminator) errors.push(fail(at, "oneOf\uC5D0\uB294 x-clack-discriminator\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
2509
+ 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."));
2510
+ const seen = /* @__PURE__ */ new Set();
2511
+ for (const raw of node.oneOf) {
2512
+ const branch = resolve9(raw);
2513
+ const tag = branch?.properties?.[discriminator];
2514
+ if (branch?.type !== "object" || typeof tag?.const !== "string" || tag.type !== "string" || !(branch.required ?? []).includes(discriminator)) {
2515
+ 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."));
2516
+ } else if (seen.has(tag.const)) errors.push(fail(at, `\uD310\uBCC4\uC790 \uAC12 ${tag.const}\uC774 \uC911\uBCF5\uB429\uB2C8\uB2E4.`));
2517
+ else seen.add(tag.const);
2518
+ }
2519
+ return;
2520
+ }
2521
+ if (node["x-clack-discriminator"] !== void 0) errors.push(fail(at, "x-clack-discriminator\uB294 oneOf\uC5D0\uB9CC \uB461\uB2C8\uB2E4."));
2522
+ if (!node.type) {
2523
+ errors.push(fail(at, "type\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."));
2524
+ return;
2525
+ }
2526
+ 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.`));
2527
+ const range = (low, high, label) => {
2528
+ 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.`));
2529
+ };
2530
+ range("minLength", "maxLength", "\uAE38\uC774");
2531
+ range("minItems", "maxItems", "\uD56D\uBAA9 \uC218");
2532
+ range("minimum", "maximum", "\uAC12");
2533
+ if (node.type === "string" && typeof node.maxLength !== "number") errors.push(fail(at, "\uBB38\uC790\uC5F4 maxLength\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."));
2534
+ 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."));
2535
+ if (node.type === "array" && node.uniqueItems === true) {
2536
+ let items = node.items;
2537
+ for (let hops = 0; typeof items?.$ref === "string" && hops < 64; hops++) items = resolve9(items);
2538
+ 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."));
2539
+ }
2540
+ if (node.type === "object") {
2541
+ 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."));
2542
+ 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.`));
2543
+ }
2544
+ 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."));
2545
+ if (node["x-clack-asset"]) {
2546
+ assetPointers.push(at);
2547
+ 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."));
2548
+ if (info.visibility === "private") errors.push(fail(at, "\uBE44\uACF5\uAC1C \uD0A4\uC5D0\uB294 \uC790\uC0B0\uC744 \uB458 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."));
2549
+ } else if (node.format === "uuid" && node.type !== "string") errors.push(fail(at, "format\uC740 \uBB38\uC790\uC5F4\uC5D0\uB9CC \uC501\uB2C8\uB2E4."));
2550
+ if (node["x-clack-ai"]?.suggest === true) {
2551
+ if (info.visibility !== "public") errors.push(fail(at, "AI \uC81C\uC548\uC740 \uACF5\uAC1C \uD0A4\uC5D0\uB9CC \uCF24 \uC218 \uC788\uC2B5\uB2C8\uB2E4."));
2552
+ suggestRoots.push(at);
2553
+ }
2554
+ });
2555
+ 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."));
2556
+ 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."));
2557
+ return errors;
2558
+ }
2559
+ var ZWJ_AFTER = new RegExp("\\p{Extended_Pictographic}", "u");
2560
+
2561
+ // src/vendor/clack-api-v4-contracts/schemas/output-data.v1.schema.json
2562
+ var output_data_v1_schema_default = {
2563
+ $schema: "https://json-schema.org/draft/2020-12/schema",
2564
+ $id: "https://clack.kr/schemas/platform/output-data.v1.schema.json",
2565
+ $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.",
2566
+ title: "\uC2A4\uD0AC \uC5D0\uB514\uD130 \uB370\uC774\uD130 \uBB38\uC11C \uC2A4\uD0A4\uB9C8 \uADDC\uCE59(output-data.v1)",
2567
+ 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.",
2568
+ type: "object",
2569
+ additionalProperties: false,
2570
+ properties: {
2571
+ $schema: {
2572
+ const: "https://json-schema.org/draft/2020-12/schema"
2573
+ },
2574
+ title: {
2575
+ type: "string",
2576
+ maxLength: 120
2577
+ },
2578
+ description: {
2579
+ type: "string",
2580
+ maxLength: 1e3
2581
+ },
2582
+ type: {
2583
+ const: "object"
2584
+ },
2585
+ additionalProperties: {
2586
+ const: false
2587
+ },
2588
+ properties: {
2589
+ type: "object",
2590
+ propertyNames: {
2591
+ $ref: "#/$defs/key"
2592
+ },
2593
+ additionalProperties: {
2594
+ $ref: "#/$defs/rootField"
2595
+ },
2596
+ minProperties: 1,
2597
+ maxProperties: 60
2598
+ },
2599
+ required: {
2600
+ $ref: "#/$defs/keyList"
2601
+ },
2602
+ $defs: {
2603
+ type: "object",
2604
+ propertyNames: {
2605
+ type: "string",
2606
+ pattern: "^[A-Za-z][A-Za-z0-9_-]{0,63}$"
2607
+ },
2608
+ additionalProperties: {
2609
+ $ref: "#/$defs/node"
2610
+ },
2611
+ maxProperties: 64
2612
+ }
2613
+ },
2614
+ required: [
2615
+ "type",
2616
+ "properties",
2617
+ "additionalProperties"
2618
+ ],
2619
+ $defs: {
2620
+ key: {
2621
+ type: "string",
2622
+ pattern: "^[a-z][a-z0-9_]{0,31}$"
2623
+ },
2624
+ keyList: {
2625
+ type: "array",
2626
+ items: {
2627
+ $ref: "#/$defs/key"
2628
+ },
2629
+ uniqueItems: true
2630
+ },
2631
+ label: {
2632
+ type: "object",
2633
+ additionalProperties: false,
2634
+ properties: {
2635
+ ko: {
2636
+ type: "string",
2637
+ minLength: 1,
2638
+ maxLength: 200
2639
+ },
2640
+ en: {
2641
+ type: "string",
2642
+ minLength: 1,
2643
+ maxLength: 200
2644
+ }
2645
+ },
2646
+ required: [
2647
+ "ko"
2648
+ ]
2649
+ },
2650
+ asset: {
2651
+ type: "object",
2652
+ additionalProperties: false,
2653
+ properties: {
2654
+ accept: {
2655
+ type: "array",
2656
+ items: {
2657
+ type: "string",
2658
+ enum: [
2659
+ "image/png",
2660
+ "image/jpeg",
2661
+ "image/webp"
2662
+ ]
2663
+ },
2664
+ minItems: 1,
2665
+ uniqueItems: true
2666
+ },
2667
+ max_bytes: {
2668
+ type: "integer",
2669
+ minimum: 1,
2670
+ maximum: 4194304
2671
+ },
2672
+ aspect: {
2673
+ type: "string",
2674
+ pattern: "^[1-9][0-9]*:[1-9][0-9]*$"
2675
+ },
2676
+ generate: {
2677
+ type: "boolean"
2678
+ }
2679
+ },
2680
+ required: [
2681
+ "accept",
2682
+ "max_bytes"
2683
+ ]
2684
+ },
2685
+ ai: {
2686
+ type: "object",
2687
+ additionalProperties: false,
2688
+ properties: {
2689
+ suggest: {
2690
+ type: "boolean"
2691
+ },
2692
+ hint: {
2693
+ type: "string",
2694
+ maxLength: 300
2695
+ }
2696
+ },
2697
+ required: [
2698
+ "suggest"
2699
+ ]
2700
+ },
2701
+ scalar: {
2702
+ type: [
2703
+ "string",
2704
+ "number",
2705
+ "boolean"
2706
+ ]
2707
+ },
2708
+ nodeKeywords: {
2709
+ type: "object",
2710
+ properties: {
2711
+ type: {
2712
+ type: "string",
2713
+ enum: [
2714
+ "object",
2715
+ "array",
2716
+ "string",
2717
+ "integer",
2718
+ "number",
2719
+ "boolean"
2720
+ ]
2721
+ },
2722
+ title: {
2723
+ type: "string",
2724
+ maxLength: 120
2725
+ },
2726
+ description: {
2727
+ type: "string",
2728
+ maxLength: 1e3
2729
+ },
2730
+ "x-clack-i18n": {
2731
+ $ref: "#/$defs/label"
2732
+ },
2733
+ enum: {
2734
+ type: "array",
2735
+ items: {
2736
+ $ref: "#/$defs/scalar"
2737
+ },
2738
+ minItems: 1,
2739
+ maxItems: 100,
2740
+ uniqueItems: true
2741
+ },
2742
+ const: {
2743
+ $ref: "#/$defs/scalar"
2744
+ },
2745
+ minLength: {
2746
+ type: "integer",
2747
+ minimum: 0
2748
+ },
2749
+ maxLength: {
2750
+ type: "integer",
2751
+ minimum: 1,
2752
+ maximum: 1e5
2753
+ },
2754
+ format: {
2755
+ const: "uuid"
2756
+ },
2757
+ minimum: {
2758
+ type: "number"
2759
+ },
2760
+ maximum: {
2761
+ type: "number"
2762
+ },
2763
+ minItems: {
2764
+ type: "integer",
2765
+ minimum: 0
2766
+ },
2767
+ maxItems: {
2768
+ type: "integer",
2769
+ minimum: 1,
2770
+ maximum: 1e3
2771
+ },
2772
+ uniqueItems: {
2773
+ type: "boolean",
2774
+ description: "\uC2A4\uCE7C\uB77C(string\xB7integer\xB7number\xB7boolean) items \uBC30\uC5F4\uC5D0\uB9CC \uC4F4\uB2E4(validators.mjs \uAC80\uC0AC)."
2775
+ },
2776
+ items: {
2777
+ $ref: "#/$defs/node"
2778
+ },
2779
+ properties: {
2780
+ type: "object",
2781
+ propertyNames: {
2782
+ $ref: "#/$defs/key"
2783
+ },
2784
+ additionalProperties: {
2785
+ $ref: "#/$defs/node"
2786
+ },
2787
+ maxProperties: 60
2788
+ },
2789
+ required: {
2790
+ $ref: "#/$defs/keyList"
2791
+ },
2792
+ additionalProperties: {
2793
+ const: false
2794
+ },
2795
+ $ref: {
2796
+ type: "string",
2797
+ pattern: "^#/\\$defs/[A-Za-z][A-Za-z0-9_-]{0,63}$"
2798
+ },
2799
+ oneOf: {
2800
+ type: "array",
2801
+ items: {
2802
+ $ref: "#/$defs/node"
2803
+ },
2804
+ minItems: 2,
2805
+ maxItems: 16
2806
+ },
2807
+ "x-clack-discriminator": {
2808
+ $ref: "#/$defs/key"
2809
+ },
2810
+ "x-clack-asset": {
2811
+ $ref: "#/$defs/asset"
2812
+ },
2813
+ "x-clack-ai": {
2814
+ $ref: "#/$defs/ai"
2815
+ }
2816
+ }
2817
+ },
2818
+ node: {
2819
+ allOf: [
2820
+ {
2821
+ $ref: "#/$defs/nodeKeywords"
2822
+ }
2823
+ ],
2824
+ unevaluatedProperties: false
2825
+ },
2826
+ rootField: {
2827
+ allOf: [
2828
+ {
2829
+ $ref: "#/$defs/nodeKeywords"
2830
+ }
2831
+ ],
2832
+ properties: {
2833
+ "x-clack-visibility": {
2834
+ type: "string",
2835
+ enum: [
2836
+ "public",
2837
+ "private"
2838
+ ]
2839
+ }
2840
+ },
2841
+ required: [
2842
+ "x-clack-visibility"
2843
+ ],
2844
+ unevaluatedProperties: false
2845
+ }
2846
+ }
2847
+ };
2848
+
2849
+ // src/lib/skill-editor.ts
2850
+ var EDITOR_BUNDLE_LIMITS = { maxFiles: 50, maxFileBytes: 512 * 1024, maxTotalBytes: 2 * 1024 * 1024, maxCodeBytes: 512 * 1024 };
2851
+ var DATA_SCHEMA_MAX_BYTES = 64 * 1024;
2852
+ var ALLOWED = /\.(?:html?|js|mjs|css|json|png|jpe?g|webp|gif|woff2?)$/i;
2853
+ var CODE = /\.(?:html?|js|mjs|css)$/i;
2854
+ var RESERVED = /^(?:runtime|form|examples|output|data)(?:\/|$)/;
2855
+ var textDecoder = new TextDecoder("utf-8", { fatal: true });
2856
+ var validateMeta = new Ajv2020({ allErrors: true, strict: false }).compile(output_data_v1_schema_default);
2857
+ var invalid = (message) => {
2858
+ throw new CliError(message, "SKILL_EDITOR_INVALID");
2859
+ };
2860
+ var isObject2 = (value2) => value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
2861
+ var sha = (value2) => createHash3("sha256").update(value2).digest("hex");
2862
+ function selectEditorBundle(files, bundle) {
2863
+ const single = Object.hasOwn(files, bundle);
2864
+ const prefix = `${bundle}/`;
2865
+ const selected = single ? [[bundle, files[bundle]]] : Object.entries(files).filter(([path]) => path.startsWith(prefix));
2866
+ 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);
2867
+ const computed = single ? list2[0]?.sha256 ?? "" : sha(JSON.stringify(list2));
2868
+ return { single, selected, list: list2, computed };
2869
+ }
2870
+ function computeEditorHash(manifest, files) {
2871
+ const editor = manifest.authoring?.editor;
2872
+ if (!isObject2(editor) || typeof editor.bundle !== "string") return null;
2873
+ const { list: list2, computed } = selectEditorBundle(files, editor.bundle.replace(/\/+$/, ""));
2874
+ return list2.length ? computed : null;
2875
+ }
2876
+ function parseJson(bytes2, label) {
2877
+ try {
2878
+ const value2 = JSON.parse(textDecoder.decode(bytes2));
2879
+ if (isObject2(value2)) return value2;
2880
+ } catch {
2881
+ }
2882
+ return invalid(`${label}\uC740 UTF-8 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
2883
+ }
2884
+ function scanBundle(list2, warnings) {
2885
+ for (const [path, data] of list2) {
2886
+ if (!/\.(?:html?|js|mjs|css)$/i.test(path)) continue;
2887
+ let text;
2888
+ try {
2889
+ text = textDecoder.decode(data);
2890
+ } catch {
2891
+ invalid(`${path}\uB294 UTF-8 \uD14D\uC2A4\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
2892
+ return;
2893
+ }
2894
+ 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.`);
2895
+ 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.`);
2896
+ if (/\.css$/i.test(path) && /url\(\s*(?!["']?data:)/i.test(text)) {
2897
+ 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.`);
2898
+ }
2899
+ }
2900
+ }
2901
+ function inspectEditorSkill(manifest, files, options2) {
2902
+ const authoring = manifest.authoring;
2903
+ const output2 = manifest.output;
2904
+ const editor = authoring.editor;
2905
+ const warnings = [];
2906
+ const bundle = String(editor.bundle).replace(/\/+$/, "");
2907
+ if (RESERVED.test(bundle)) invalid("editor \uBC88\uB4E4 \uACBD\uB85C\uAC00 \uC608\uC57D \uACBD\uB85C(runtime\xB7form\xB7examples\xB7output\xB7data)\uC785\uB2C8\uB2E4.");
2908
+ const { single, selected, list: list2, computed } = selectEditorBundle(files, bundle);
2909
+ if (!list2.length) invalid(`editor \uBC88\uB4E4(${bundle})\uC5D0 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`);
2910
+ const entry = single ? bundle : `${bundle}/index.html`;
2911
+ if (single ? !/\.html?$/i.test(bundle) : !list2.some((file) => file.path === "index.html")) invalid(`editor \uC9C4\uC785\uC810(${entry})\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.`);
2912
+ 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.`);
2913
+ const total = list2.reduce((sum, file) => sum + file.bytes, 0);
2914
+ if (total > EDITOR_BUNDLE_LIMITS.maxTotalBytes) invalid("editor \uBC88\uB4E4 \uC804\uCCB4 \uD06C\uAE30\uAC00 2 MiB\uB97C \uB118\uC2B5\uB2C8\uB2E4.");
2915
+ let code = 0;
2916
+ for (const [path, data2] of selected) {
2917
+ if (!ALLOWED.test(path)) invalid(`editor \uBC88\uB4E4\uC5D0 \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4: ${path}`);
2918
+ if (data2.length > EDITOR_BUNDLE_LIMITS.maxFileBytes) invalid(`editor \uBC88\uB4E4 \uD30C\uC77C\uC774 512 KiB\uB97C \uB118\uC2B5\uB2C8\uB2E4: ${path}`);
2919
+ if (CODE.test(path)) code += data2.length;
2920
+ }
2921
+ 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.");
2922
+ scanBundle(selected, warnings);
2923
+ const declared = String(editor.sha256);
2924
+ const match = declared === computed;
2925
+ if (!match) {
2926
+ 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.`;
2927
+ if (options2.hashMismatch === "error") invalid(message);
2928
+ warnings.push(`PLUGIN_BUNDLE_HASH_MISMATCH: ${message}`);
2929
+ }
2930
+ const data = output2.data;
2931
+ const schemaPath = data?.schema;
2932
+ if (typeof schemaPath !== "string" || !files[schemaPath]) return invalid("output.data.schema \uD30C\uC77C\uC774 \uD328\uD0A4\uC9C0\uC5D0 \uC5C6\uC2B5\uB2C8\uB2E4.");
2933
+ 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.");
2934
+ const schema = parseJson(files[schemaPath], "\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8");
2935
+ if (!validateMeta(schema)) {
2936
+ 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(" / ")}`);
2937
+ }
2938
+ const dataErrors = [...inspectOutputData(schema), ...inspectAuthoringSemantics(manifest, void 0, schema)];
2939
+ if (dataErrors.length) invalid(dataErrors.map((error2) => `${error2.path}: ${error2.message}`).join(" / "));
2940
+ const template = output2.template;
2941
+ if (!template || template.root !== "runtime" || typeof template.entry !== "string") invalid("runtime \uD15C\uD50C\uB9BF \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
2942
+ 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).");
2943
+ const examples = Object.keys(files).filter((path) => /^examples\/[^/]+\.json$/.test(path));
2944
+ if (!examples.length || examples.length > 10) invalid("\uD15C\uD50C\uB9BF \uC608\uC2DC\uB294 1~10\uAC1C \uD544\uC694\uD569\uB2C8\uB2E4.");
2945
+ let validateExample;
2946
+ try {
2947
+ const ajv2 = new Ajv2020({ allErrors: true, strict: false });
2948
+ 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);
2949
+ validateExample = ajv2.compile(schema);
2950
+ } catch {
2951
+ return invalid("\uB370\uC774\uD130 \uC2A4\uD0A4\uB9C8\uB97C \uCEF4\uD30C\uC77C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
2952
+ }
2953
+ for (const path of examples) {
2954
+ if (files[path].length > 256 * 1024) invalid(`\uC608\uC2DC\uAC00 \uB108\uBB34 \uD07D\uB2C8\uB2E4: ${path}`);
2955
+ 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}`);
2956
+ }
2957
+ return { computedHash: computed, warnings, summary: {
2958
+ bundle,
2959
+ entry,
2960
+ file_count: list2.length,
2961
+ total_bytes: total,
2962
+ declared_sha256: declared,
2963
+ computed_sha256: computed,
2964
+ hash_match: match,
2965
+ permissions: editor.permissions,
2966
+ center_only: true
2967
+ } };
2968
+ }
2969
+
2218
2970
  // src/vendor/clack-api-v4-contracts/schemas/clack-skill.v1.schema.json
2219
2971
  var clack_skill_v1_schema_default = {
2220
2972
  $schema: "https://json-schema.org/draft/2020-12/schema",
@@ -2454,11 +3206,62 @@ var clack_skill_v1_schema_default = {
2454
3206
  "purpose"
2455
3207
  ]
2456
3208
  },
2457
- maxItems: 8
3209
+ maxItems: 8
3210
+ },
3211
+ plugin: {
3212
+ type: "object",
3213
+ additionalProperties: false,
3214
+ properties: {
3215
+ bundle: {
3216
+ type: "string",
3217
+ pattern: "^(?!/)(?!.*\\.\\.)[A-Za-z0-9_./-]+$"
3218
+ },
3219
+ sha256: {
3220
+ type: "string",
3221
+ pattern: "^[a-f0-9]{64}$"
3222
+ },
3223
+ fields: {
3224
+ type: "array",
3225
+ items: {
3226
+ type: "string",
3227
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
3228
+ },
3229
+ minItems: 1,
3230
+ uniqueItems: true
3231
+ },
3232
+ permissions: {
3233
+ type: "array",
3234
+ items: {
3235
+ type: "string",
3236
+ enum: [
3237
+ "getFormSchema",
3238
+ "getFormValues",
3239
+ "setFormValues",
3240
+ "listApprovedAssets",
3241
+ "requestAssetUpload"
3242
+ ]
3243
+ },
3244
+ minItems: 1,
3245
+ uniqueItems: true
3246
+ },
3247
+ purpose: {
3248
+ type: "string",
3249
+ minLength: 1,
3250
+ maxLength: 200
3251
+ }
3252
+ },
3253
+ required: [
3254
+ "bundle",
3255
+ "sha256",
3256
+ "fields",
3257
+ "permissions",
3258
+ "purpose"
3259
+ ]
2458
3260
  },
2459
- plugin: {
3261
+ editor: {
2460
3262
  type: "object",
2461
3263
  additionalProperties: false,
3264
+ 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.",
2462
3265
  properties: {
2463
3266
  bundle: {
2464
3267
  type: "string",
@@ -2468,40 +3271,47 @@ var clack_skill_v1_schema_default = {
2468
3271
  type: "string",
2469
3272
  pattern: "^[a-f0-9]{64}$"
2470
3273
  },
2471
- fields: {
2472
- type: "array",
2473
- items: {
2474
- type: "string",
2475
- pattern: "^[a-z][a-z0-9_-]{0,31}$"
2476
- },
2477
- minItems: 1,
2478
- uniqueItems: true
2479
- },
2480
3274
  permissions: {
2481
3275
  type: "array",
2482
3276
  items: {
2483
3277
  type: "string",
2484
3278
  enum: [
2485
- "getFormSchema",
2486
- "getFormValues",
2487
- "setFormValues",
2488
- "listApprovedAssets",
2489
- "requestAssetUpload"
3279
+ "document.read",
3280
+ "document.write",
3281
+ "assets.read",
3282
+ "assets.pick",
3283
+ "ai.suggest",
3284
+ "tools.image.generate",
3285
+ "preview",
3286
+ "package"
2490
3287
  ]
2491
3288
  },
2492
- minItems: 1,
3289
+ minItems: 2,
2493
3290
  uniqueItems: true
2494
3291
  },
2495
3292
  purpose: {
2496
- type: "string",
2497
- minLength: 1,
2498
- maxLength: 200
3293
+ type: "object",
3294
+ additionalProperties: false,
3295
+ properties: {
3296
+ ko: {
3297
+ type: "string",
3298
+ minLength: 1,
3299
+ maxLength: 200
3300
+ },
3301
+ en: {
3302
+ type: "string",
3303
+ minLength: 1,
3304
+ maxLength: 200
3305
+ }
3306
+ },
3307
+ required: [
3308
+ "ko"
3309
+ ]
2499
3310
  }
2500
3311
  },
2501
3312
  required: [
2502
3313
  "bundle",
2503
3314
  "sha256",
2504
- "fields",
2505
3315
  "permissions",
2506
3316
  "purpose"
2507
3317
  ]
@@ -2766,7 +3576,8 @@ var clack_skill_v1_schema_default = {
2766
3576
  "fixed",
2767
3577
  "scroll_hide",
2768
3578
  "translucent_scroll_hide",
2769
- "floating_close"
3579
+ "floating_close",
3580
+ "floating_close_safe_area"
2770
3581
  ]
2771
3582
  },
2772
3583
  color_scheme: {
@@ -2781,6 +3592,89 @@ var clack_skill_v1_schema_default = {
2781
3592
  "header_mode",
2782
3593
  "color_scheme"
2783
3594
  ]
3595
+ },
3596
+ data: {
3597
+ type: "object",
3598
+ additionalProperties: false,
3599
+ 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.",
3600
+ properties: {
3601
+ schema: {
3602
+ type: "string",
3603
+ pattern: "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[A-Za-z0-9_./-]+$"
3604
+ },
3605
+ max_bytes: {
3606
+ type: "integer",
3607
+ minimum: 1024,
3608
+ maximum: 262144
3609
+ },
3610
+ private_max_bytes: {
3611
+ type: "integer",
3612
+ minimum: 1,
3613
+ maximum: 65536
3614
+ },
3615
+ assets: {
3616
+ type: "object",
3617
+ additionalProperties: false,
3618
+ properties: {
3619
+ max_count: {
3620
+ type: "integer",
3621
+ minimum: 1,
3622
+ maximum: 48
3623
+ },
3624
+ max_total_bytes: {
3625
+ type: "integer",
3626
+ minimum: 1,
3627
+ maximum: 25165824
3628
+ }
3629
+ },
3630
+ required: [
3631
+ "max_count",
3632
+ "max_total_bytes"
3633
+ ]
3634
+ }
3635
+ },
3636
+ required: [
3637
+ "schema"
3638
+ ]
3639
+ },
3640
+ intro: {
3641
+ type: "object",
3642
+ additionalProperties: false,
3643
+ 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.",
3644
+ properties: {
3645
+ default_enabled: {
3646
+ type: "boolean",
3647
+ default: false
3648
+ },
3649
+ creator_comment: {
3650
+ type: "string",
3651
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
3652
+ },
3653
+ description: {
3654
+ type: "string",
3655
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
3656
+ },
3657
+ description_fallback: {
3658
+ type: "array",
3659
+ items: {
3660
+ type: "string",
3661
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
3662
+ },
3663
+ minItems: 1,
3664
+ maxItems: 20,
3665
+ uniqueItems: true
3666
+ },
3667
+ images: {
3668
+ type: "array",
3669
+ items: {
3670
+ type: "string",
3671
+ pattern: "^[a-z][a-z0-9_-]{0,31}$"
3672
+ },
3673
+ minItems: 1,
3674
+ maxItems: 8,
3675
+ uniqueItems: true
3676
+ }
3677
+ }
2784
3678
  }
2785
3679
  },
2786
3680
  required: [
@@ -2923,8 +3817,27 @@ var clack_skill_v1_schema_default = {
2923
3817
  ],
2924
3818
  properties: {
2925
3819
  authoring: {
2926
- required: [
2927
- "form"
3820
+ oneOf: [
3821
+ {
3822
+ required: [
3823
+ "form"
3824
+ ],
3825
+ not: {
3826
+ required: [
3827
+ "editor"
3828
+ ]
3829
+ }
3830
+ },
3831
+ {
3832
+ required: [
3833
+ "editor"
3834
+ ],
3835
+ not: {
3836
+ required: [
3837
+ "form"
3838
+ ]
3839
+ }
3840
+ }
2928
3841
  ],
2929
3842
  properties: {
2930
3843
  modes: {
@@ -2975,6 +3888,136 @@ var clack_skill_v1_schema_default = {
2975
3888
  "runtime"
2976
3889
  ]
2977
3890
  }
3891
+ },
3892
+ {
3893
+ if: {
3894
+ properties: {
3895
+ authoring: {
3896
+ required: [
3897
+ "editor"
3898
+ ]
3899
+ }
3900
+ },
3901
+ required: [
3902
+ "authoring"
3903
+ ]
3904
+ },
3905
+ then: {
3906
+ properties: {
3907
+ type: {
3908
+ const: "template"
3909
+ },
3910
+ authoring: {
3911
+ properties: {
3912
+ surfaces: {
3913
+ items: {
3914
+ const: "center"
3915
+ },
3916
+ maxItems: 1
3917
+ }
3918
+ },
3919
+ not: {
3920
+ anyOf: [
3921
+ {
3922
+ required: [
3923
+ "form"
3924
+ ]
3925
+ },
3926
+ {
3927
+ required: [
3928
+ "plugin"
3929
+ ]
3930
+ }
3931
+ ]
3932
+ }
3933
+ },
3934
+ output: {
3935
+ required: [
3936
+ "data"
3937
+ ],
3938
+ not: {
3939
+ anyOf: [
3940
+ {
3941
+ required: [
3942
+ "asset_slots"
3943
+ ]
3944
+ },
3945
+ {
3946
+ required: [
3947
+ "derive"
3948
+ ]
3949
+ },
3950
+ {
3951
+ required: [
3952
+ "profile_select"
3953
+ ]
3954
+ },
3955
+ {
3956
+ required: [
3957
+ "entity"
3958
+ ]
3959
+ },
3960
+ {
3961
+ required: [
3962
+ "entity_map"
3963
+ ]
3964
+ },
3965
+ {
3966
+ required: [
3967
+ "intro"
3968
+ ]
3969
+ }
3970
+ ]
3971
+ }
3972
+ }
3973
+ }
3974
+ }
3975
+ },
3976
+ {
3977
+ if: {
3978
+ properties: {
3979
+ output: {
3980
+ required: [
3981
+ "data"
3982
+ ]
3983
+ }
3984
+ },
3985
+ required: [
3986
+ "output"
3987
+ ]
3988
+ },
3989
+ then: {
3990
+ properties: {
3991
+ authoring: {
3992
+ required: [
3993
+ "editor"
3994
+ ]
3995
+ }
3996
+ }
3997
+ }
3998
+ },
3999
+ {
4000
+ if: {
4001
+ properties: {
4002
+ type: {
4003
+ const: "instruction"
4004
+ }
4005
+ },
4006
+ required: [
4007
+ "type"
4008
+ ]
4009
+ },
4010
+ then: {
4011
+ properties: {
4012
+ output: {
4013
+ not: {
4014
+ required: [
4015
+ "intro"
4016
+ ]
4017
+ }
4018
+ }
4019
+ }
4020
+ }
2978
4021
  }
2979
4022
  ]
2980
4023
  };
@@ -3858,39 +4901,39 @@ var clack_content_v1_schema_default = {
3858
4901
  var MAX_ZIP = 10 * 1024 * 1024;
3859
4902
  var MAX_EXPANDED = 30 * 1024 * 1024;
3860
4903
  var MAX_FILES2 = 500;
3861
- var textDecoder = new TextDecoder("utf-8", { fatal: true });
3862
- var ajv = new Ajv2020({ allErrors: true, strict: false });
4904
+ var textDecoder2 = new TextDecoder("utf-8", { fatal: true });
4905
+ var ajv = new Ajv20202({ allErrors: true, strict: false });
3863
4906
  ajv.addSchema(clack_content_v1_schema_default);
3864
4907
  var validateManifest = ajv.compile(clack_skill_v1_schema_default);
3865
- var validateForm = new Ajv2020({ allErrors: true, strict: false }).compile(authoring_form_v1_schema_default);
3866
- function invalid(message) {
4908
+ var validateForm = new Ajv20202({ allErrors: true, strict: false }).compile(authoring_form_v1_schema_default);
4909
+ function invalid2(message) {
3867
4910
  throw new CliError(message, "SKILL_PACKAGE_INVALID");
3868
4911
  }
3869
- function hash(bytes) {
3870
- return createHash3("sha256").update(bytes).digest("hex");
4912
+ function hash(bytes2) {
4913
+ return createHash4("sha256").update(bytes2).digest("hex");
3871
4914
  }
3872
4915
  function object(value2) {
3873
4916
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
3874
4917
  }
3875
- function json(bytes, max, label) {
3876
- if (bytes.length > max) invalid(`${label} \uD06C\uAE30 \uC81C\uD55C\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.`);
4918
+ function json(bytes2, max, label) {
4919
+ if (bytes2.length > max) invalid2(`${label} \uD06C\uAE30 \uC81C\uD55C\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.`);
3877
4920
  try {
3878
- const parsed = JSON.parse(textDecoder.decode(bytes));
4921
+ const parsed = JSON.parse(textDecoder2.decode(bytes2));
3879
4922
  if (object(parsed)) return parsed;
3880
4923
  } catch {
3881
4924
  }
3882
- return invalid(`${label}\uC740 UTF-8 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
4925
+ return invalid2(`${label}\uC740 UTF-8 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
3883
4926
  }
3884
4927
  async function readRegular(path) {
3885
4928
  let handle;
3886
4929
  try {
3887
4930
  handle = await open3(path, constants.O_RDONLY | constants.O_NOFOLLOW);
3888
4931
  const info = await handle.stat();
3889
- if (!info.isFile()) invalid("\uC2A4\uD0AC \uD328\uD0A4\uC9C0\uC5D0\uB294 \uC77C\uBC18 \uD30C\uC77C\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.");
4932
+ if (!info.isFile()) invalid2("\uC2A4\uD0AC \uD328\uD0A4\uC9C0\uC5D0\uB294 \uC77C\uBC18 \uD30C\uC77C\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.");
3890
4933
  return await handle.readFile();
3891
4934
  } catch (error2) {
3892
4935
  if (error2 instanceof CliError) throw error2;
3893
- return invalid("\uC2A4\uD0AC \uD30C\uC77C\uC744 \uC548\uC804\uD558\uAC8C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4936
+ return invalid2("\uC2A4\uD0AC \uD30C\uC77C\uC744 \uC548\uC804\uD558\uAC8C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
3894
4937
  } finally {
3895
4938
  await handle?.close();
3896
4939
  }
@@ -3909,76 +4952,76 @@ async function collectFiles(directory) {
3909
4952
  const entries = await readdir(join2(directory, relative), { withFileTypes: true });
3910
4953
  for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
3911
4954
  const path = relative ? `${relative}/${entry.name}` : entry.name;
3912
- if (!skillPathAllowed(path)) invalid(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD328\uD0A4\uC9C0 \uACBD\uB85C: ${path}`);
3913
- if (entry.isSymbolicLink() || !(entry.isDirectory() || entry.isFile())) invalid(`\uC77C\uBC18 \uD30C\uC77C\xB7\uD3F4\uB354\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4: ${path}`);
4955
+ if (!skillPathAllowed(path)) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD328\uD0A4\uC9C0 \uACBD\uB85C: ${path}`);
4956
+ if (entry.isSymbolicLink() || !(entry.isDirectory() || entry.isFile())) invalid2(`\uC77C\uBC18 \uD30C\uC77C\xB7\uD3F4\uB354\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4: ${path}`);
3914
4957
  if (entry.isDirectory()) {
3915
4958
  await walk(path);
3916
4959
  continue;
3917
4960
  }
3918
- if (Object.keys(files).length >= MAX_FILES2) invalid("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
4961
+ if (Object.keys(files).length >= MAX_FILES2) invalid2("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
3919
4962
  const data = await readRegular(join2(directory, path));
3920
4963
  totalBytes += data.length;
3921
- if (totalBytes > MAX_EXPANDED) invalid("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
4964
+ if (totalBytes > MAX_EXPANDED) invalid2("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
3922
4965
  files[path] = data;
3923
4966
  }
3924
4967
  }
3925
4968
  await walk("");
3926
4969
  return files;
3927
4970
  }
3928
- function inspectZip(bytes, expectedDirectory) {
3929
- 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.");
4971
+ function inspectZip(bytes2, expectedDirectory, hashMismatch) {
4972
+ 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.");
3930
4973
  const seen = /* @__PURE__ */ new Set();
3931
4974
  let entries = 0;
3932
4975
  let totalBytes = 0;
3933
4976
  let extracted;
3934
4977
  try {
3935
- extracted = unzipSync2(bytes, { filter(entry) {
4978
+ extracted = unzipSync2(bytes2, { filter(entry) {
3936
4979
  entries++;
3937
- if (entries > MAX_FILES2) invalid("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
4980
+ if (entries > MAX_FILES2) invalid2("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
3938
4981
  const path = entry.name.endsWith("/") ? entry.name.slice(0, -1) : entry.name;
3939
- if (!skillPathAllowed(path) || seen.has(path.toLowerCase())) invalid(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${path}`);
4982
+ if (!skillPathAllowed(path) || seen.has(path.toLowerCase())) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${path}`);
3940
4983
  seen.add(path.toLowerCase());
3941
4984
  if (entry.name.endsWith("/")) return false;
3942
4985
  totalBytes += entry.originalSize;
3943
- if (totalBytes > MAX_EXPANDED) invalid("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
4986
+ if (totalBytes > MAX_EXPANDED) invalid2("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
3944
4987
  return true;
3945
4988
  } });
3946
4989
  } catch (error2) {
3947
4990
  if (error2 instanceof CliError) throw error2;
3948
- return invalid("ZIP \uAD6C\uC870\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4991
+ return invalid2("ZIP \uAD6C\uC870\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
3949
4992
  }
3950
4993
  const markdown = extracted["SKILL.md"];
3951
4994
  const manifestBytes = extracted["clack.skill.json"];
3952
- if (!markdown || !manifestBytes) invalid("\uB8E8\uD2B8 SKILL.md\uC640 clack.skill.json\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
3953
- if (markdown.length > 20 * 1024) invalid("SKILL.md\uB294 20 KiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
4995
+ if (!markdown || !manifestBytes) invalid2("\uB8E8\uD2B8 SKILL.md\uC640 clack.skill.json\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
4996
+ if (markdown.length > 20 * 1024) invalid2("SKILL.md\uB294 20 KiB \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
3954
4997
  let frontmatter;
3955
4998
  try {
3956
- const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(textDecoder.decode(markdown));
3957
- if (!match) invalid("SKILL.md\uC758 YAML frontmatter\uB97C \uD655\uC778\uD558\uC138\uC694.");
4999
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(textDecoder2.decode(markdown));
5000
+ if (!match) invalid2("SKILL.md\uC758 YAML frontmatter\uB97C \uD655\uC778\uD558\uC138\uC694.");
3958
5001
  frontmatter = match[1];
3959
5002
  } catch {
3960
- return invalid("SKILL.md\uB294 UTF-8 \uD14D\uC2A4\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
5003
+ return invalid2("SKILL.md\uB294 UTF-8 \uD14D\uC2A4\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
3961
5004
  }
3962
5005
  const document = parseDocument(frontmatter, { version: "1.2", uniqueKeys: true, merge: false });
3963
5006
  if (document.errors.length || document.warnings.length || /(?:^|\n)\s*allowed-tools\s*:/i.test(frontmatter)) {
3964
- invalid("SKILL.md frontmatter\uC5D0 \uC798\uBABB\uB41C \uAC12 \uB610\uB294 allowed-tools\uAC00 \uC788\uC2B5\uB2C8\uB2E4.");
5007
+ invalid2("SKILL.md frontmatter\uC5D0 \uC798\uBABB\uB41C \uAC12 \uB610\uB294 allowed-tools\uAC00 \uC788\uC2B5\uB2C8\uB2E4.");
3965
5008
  }
3966
5009
  let front;
3967
5010
  try {
3968
5011
  front = document.toJS({ maxAliasCount: 0 });
3969
5012
  } catch {
3970
- return invalid("SKILL.md frontmatter\uB97C \uD655\uC778\uD558\uC138\uC694.");
5013
+ return invalid2("SKILL.md frontmatter\uB97C \uD655\uC778\uD558\uC138\uC694.");
3971
5014
  }
3972
- 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.");
5015
+ 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.");
3973
5016
  const manifest = json(manifestBytes, 64 * 1024, "clack.skill.json");
3974
- if (!validateManifest(manifest)) invalid("clack.skill.json\uC774 \uACF5\uAC1C \uC2A4\uD0AC \uC2A4\uD0A4\uB9C8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
5017
+ if (!validateManifest(manifest)) invalid2("clack.skill.json\uC774 \uACF5\uAC1C \uC2A4\uD0AC \uC2A4\uD0A4\uB9C8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
3975
5018
  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) {
3976
- 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.");
5019
+ 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.");
3977
5020
  }
3978
5021
  const file = (path, max) => {
3979
- 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.");
5022
+ 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.");
3980
5023
  const data = extracted[path];
3981
- if (max && data.length > max) invalid("\uC120\uC5B8\uD55C \uD30C\uC77C\uC758 \uD06C\uAE30 \uC81C\uD55C\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
5024
+ if (max && data.length > max) invalid2("\uC120\uC5B8\uD55C \uD30C\uC77C\uC758 \uD06C\uAE30 \uC81C\uD55C\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
3982
5025
  return data;
3983
5026
  };
3984
5027
  const display = manifest.display;
@@ -3987,50 +5030,93 @@ function inspectZip(bytes, expectedDirectory) {
3987
5030
  const authoring = manifest.authoring;
3988
5031
  const output2 = manifest.output;
3989
5032
  for (const reference of authoring.references ?? []) file(reference, 64 * 1024);
3990
- if (manifest.type === "template" || manifest.type === "tool") {
5033
+ let editor;
5034
+ if (manifest.type === "template" && authoring.editor) {
5035
+ editor = inspectEditorSkill(manifest, extracted, { hashMismatch });
5036
+ file(`runtime/${output2.template.entry}`);
5037
+ const runtime2 = manifest.runtime;
5038
+ for (const profile of runtime2.profiles) file(`runtime/.clack/profiles/${profile}.yaml`, 16 * 1024);
5039
+ } else if (manifest.type === "template" || manifest.type === "tool") {
3991
5040
  const form = json(file(authoring.form, 64 * 1024), 64 * 1024, "\uC81C\uC791 \uD3FC");
3992
- if (!validateForm(form)) invalid("\uC81C\uC791 \uD3FC\uC774 \uACF5\uAC1C \uC2A4\uD0A4\uB9C8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
5041
+ if (!validateForm(form)) invalid2("\uC81C\uC791 \uD3FC\uC774 \uACF5\uAC1C \uC2A4\uD0A4\uB9C8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
3993
5042
  const semanticErrors = inspectAuthoringSemantics(manifest, form);
3994
- if (semanticErrors.length) invalid(semanticErrors.map((error2) => `${error2.path}: ${error2.message}`).join(" / "));
5043
+ if (semanticErrors.length) invalid2(semanticErrors.map((error2) => `${error2.path}: ${error2.message}`).join(" / "));
3995
5044
  const template = output2.template;
3996
- if (template.root !== "runtime" || typeof template.entry !== "string") invalid("runtime \uD15C\uD50C\uB9BF \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
5045
+ if (template.root !== "runtime" || typeof template.entry !== "string") invalid2("runtime \uD15C\uD50C\uB9BF \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
3997
5046
  file(`runtime/${template.entry}`);
3998
5047
  const runtime2 = manifest.runtime;
3999
5048
  for (const profile of runtime2.profiles) file(`runtime/.clack/profiles/${profile}.yaml`, 16 * 1024);
4000
5049
  const examples = Object.keys(extracted).filter((path) => /^examples\/[^/]+\.json$/.test(path));
4001
- if (!examples.length || examples.length > 10) invalid("\uD15C\uD50C\uB9BF \uC608\uC2DC\uB294 1~10\uAC1C \uD544\uC694\uD569\uB2C8\uB2E4.");
5050
+ if (!examples.length || examples.length > 10) invalid2("\uD15C\uD50C\uB9BF \uC608\uC2DC\uB294 1~10\uAC1C \uD544\uC694\uD569\uB2C8\uB2E4.");
4002
5051
  let validateExample;
4003
5052
  try {
4004
- const examplesAjv = new Ajv2020({ allErrors: true, strict: false });
5053
+ const examplesAjv = new Ajv20202({ allErrors: true, strict: false });
4005
5054
  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);
4006
5055
  validateExample = examplesAjv.compile(form);
4007
5056
  } catch {
4008
- return invalid("\uC81C\uC791 \uD3FC JSON Schema\uB97C \uD655\uC778\uD558\uC138\uC694.");
5057
+ return invalid2("\uC81C\uC791 \uD3FC JSON Schema\uB97C \uD655\uC778\uD558\uC138\uC694.");
4009
5058
  }
4010
5059
  for (const path of examples) if (!validateExample(json(file(path, 64 * 1024), 64 * 1024, "\uC608\uC2DC"))) {
4011
- invalid("\uC81C\uC791 \uD3FC\uACFC \uC608\uC2DC \uB370\uC774\uD130\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
5060
+ invalid2("\uC81C\uC791 \uD3FC\uACFC \uC608\uC2DC \uB370\uC774\uD130\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
4012
5061
  }
4013
5062
  }
4014
- return { manifest, file_count: Object.keys(extracted).length, total_bytes: totalBytes };
5063
+ return {
5064
+ manifest,
5065
+ file_count: Object.keys(extracted).length,
5066
+ total_bytes: totalBytes,
5067
+ ...editor ? { editor: editor.summary, warnings: editor.warnings } : {}
5068
+ };
5069
+ }
5070
+ function fillEditorHash(files) {
5071
+ const manifestBytes = files["clack.skill.json"];
5072
+ if (!manifestBytes) return null;
5073
+ let text;
5074
+ let manifest;
5075
+ try {
5076
+ text = textDecoder2.decode(manifestBytes);
5077
+ manifest = JSON.parse(text);
5078
+ } catch {
5079
+ return null;
5080
+ }
5081
+ if (!object(manifest)) return null;
5082
+ const computed = computeEditorHash(manifest, files);
5083
+ const editor = object(manifest.authoring) ? manifest.authoring.editor : void 0;
5084
+ if (!computed || !object(editor) || typeof editor.sha256 !== "string" || editor.sha256 === computed) return null;
5085
+ const declared = editor.sha256;
5086
+ const next = text.split(declared).length === 2 ? text.replace(declared, computed) : `${JSON.stringify({ ...manifest, authoring: { ...manifest.authoring, editor: { ...editor, sha256: computed } } }, null, 2)}
5087
+ `;
5088
+ return new TextEncoder().encode(next);
4015
5089
  }
4016
- async function prepareSkillPackage(source) {
5090
+ async function prepareSkillPackage(source, options2 = {}) {
4017
5091
  const path = resolve7(source);
4018
5092
  let info;
4019
5093
  try {
4020
5094
  info = await lstat4(path);
4021
5095
  } catch {
4022
- return invalid("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5096
+ return invalid2("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4023
5097
  }
4024
- if (info.isSymbolicLink()) invalid("\uC2A4\uD0AC \uACBD\uB85C\uB294 \uC2EC\uBCFC\uB9AD \uB9C1\uD06C\uC77C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
4025
- let bytes;
5098
+ if (info.isSymbolicLink()) invalid2("\uC2A4\uD0AC \uACBD\uB85C\uB294 \uC2EC\uBCFC\uB9AD \uB9C1\uD06C\uC77C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
5099
+ let bytes2;
5100
+ let filledManifest;
4026
5101
  if (info.isDirectory()) {
4027
5102
  const files = await collectFiles(path);
4028
- if (!Object.keys(files).length) invalid("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");
4029
- bytes = Buffer.from(zipSync(files, { level: 6, mtime: new Date(2e3, 0, 1) }));
4030
- } else if (info.isFile() && extname2(path).toLowerCase() === ".zip") bytes = await readRegular(path);
4031
- else return invalid("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.");
4032
- const inspected = inspectZip(bytes, info.isDirectory() ? basename4(path) : void 0);
4033
- return { bytes, sha256: hash(bytes), ...inspected };
5103
+ if (!Object.keys(files).length) invalid2("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC\uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4.");
5104
+ if (options2.fillEditorHash) {
5105
+ const filled = fillEditorHash(files);
5106
+ if (filled) {
5107
+ files["clack.skill.json"] = filled;
5108
+ filledManifest = filled;
5109
+ }
5110
+ }
5111
+ bytes2 = Buffer.from(zipSync(files, { level: 6, mtime: new Date(2e3, 0, 1) }));
5112
+ } else if (info.isFile() && extname2(path).toLowerCase() === ".zip") bytes2 = await readRegular(path);
5113
+ else return invalid2("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.");
5114
+ const inspected = inspectZip(
5115
+ bytes2,
5116
+ info.isDirectory() ? basename4(path) : void 0,
5117
+ info.isDirectory() && !options2.fillEditorHash ? "warn" : "error"
5118
+ );
5119
+ return { bytes: bytes2, sha256: hash(bytes2), ...inspected, ...filledManifest ? { filled_manifest: filledManifest } : {} };
4034
5120
  }
4035
5121
 
4036
5122
  // src/commands/skill.ts
@@ -4061,14 +5147,19 @@ function serverData(schema, value2) {
4061
5147
  if (!parsed.success) throw new CliError("\uC2A4\uD0AC API \uC751\uB2F5 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.", "INVALID_RESPONSE", 502);
4062
5148
  return parsed.data;
4063
5149
  }
4064
- function ensurePat(client, message = "\uC2A4\uD0AC \uC5C5\uB85C\uB4DC\uC5D0\uB294 skill:write \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.") {
5150
+ var PAT_FOR_WRITE = "\uC2A4\uD0AC \uC11C\uBC84 \uAC80\uC99D\xB7\uC5C5\uB85C\uB4DC\xB7\uC644\uB8CC \uCC98\uB9AC\uC5D0\uB294 skill:write \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
5151
+ var PAT_FOR_SUBMIT = "\uC2A4\uD0AC \uC2EC\uC0AC \uC81C\uCD9C\uC5D0\uB294 skill:write\uC640 skill:publish \uAD8C\uD55C(\uB458 \uB2E4)\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
5152
+ var PAT_FOR_CANCEL = "\uC2A4\uD0AC \uC5C5\uB85C\uB4DC\xB7\uC2EC\uC0AC \uCDE8\uC18C\uC5D0\uB294 skill:write \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
5153
+ var PAT_FOR_RELEASE = "\uC2A4\uD0AC \uAC8C\uC2DC\uC5D0\uB294 skill:publish \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
5154
+ var PAT_FOR_DEPRECATE = "\uC2A4\uD0AC \uC9C0\uC6D0 \uC885\uB8CC\uC5D0\uB294 skill:publish \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.";
5155
+ function ensurePat(client, message = PAT_FOR_WRITE) {
4065
5156
  const token = client.settings.token;
4066
5157
  if (!token || !/^pat_[a-f0-9]{64}$/.test(token)) {
4067
5158
  throw new CliError(message, "SKILL_PAT_REQUIRED", 401);
4068
5159
  }
4069
5160
  return token;
4070
5161
  }
4071
- async function validateSkillRemotely(client, bytes) {
5162
+ async function validateSkillRemotely(client, bytes2) {
4072
5163
  const token = ensurePat(client);
4073
5164
  const url = new URL("/v4/creator/skills/validate", client.settings.baseUrl);
4074
5165
  let response;
@@ -4086,7 +5177,7 @@ async function validateSkillRemotely(client, bytes) {
4086
5177
  "X-CLACK-Time-Contract": "utc-v1",
4087
5178
  "Cache-Control": "no-store"
4088
5179
  },
4089
- body: new Uint8Array(bytes),
5180
+ body: new Uint8Array(bytes2),
4090
5181
  redirect: "error",
4091
5182
  signal: AbortSignal.timeout(3e4)
4092
5183
  });
@@ -4146,8 +5237,8 @@ async function pushSkillVersion(client, prepared, skillId) {
4146
5237
  }
4147
5238
  }
4148
5239
  function registerSkillCommands(program2, runtime2) {
4149
- const skill = program2.command("skill").description("\uC2A4\uD0AC \uD328\uD0A4\uC9C0 \uAC80\uC0AC\xB7\uC5C5\uB85C\uB4DC");
4150
- runtime2.action(skill.command("list").option("-q, --query <text>", "\uC2A4\uD0AC \uC774\uB984 \uAC80\uC0C9").option("--category <category>", "\uBD84\uB958").option("--entity <entity>", "\uC0B0\uCD9C\uBB3C \uC5D4\uD2F0\uD2F0(character)").option("--type <type>", "\uC2A4\uD0AC \uC720\uD615(template|instruction|tool)").option("--official <value>", "\uACF5\uC2DD \uC2A4\uD0AC\uB9CC true \uB610\uB294 false").option("--cursor <id>", "\uB2E4\uC74C \uD398\uC774\uC9C0 UUID").option("--all", "\uBAA8\uB4E0 \uD398\uC774\uC9C0 \uC870\uD68C"), async (ctx, _args, opts) => {
5240
+ const skill = program2.command("skill").description("\uC2A4\uD0AC \uD328\uD0A4\uC9C0 \uAC80\uC0AC\xB7\uC5C5\uB85C\uB4DC\xB7\uC2EC\uC0AC\xB7\uAC8C\uC2DC (skill:read\xB7write\xB7publish)");
5241
+ runtime2.action(skill.command("list").description("\uC2A4\uD0AC \uBAA9\uB85D \uAC80\uC0C9 (skill:read)").option("-q, --query <text>", "\uC2A4\uD0AC \uC774\uB984 \uAC80\uC0C9").option("--category <category>", "\uBD84\uB958").option("--entity <entity>", "\uC0B0\uCD9C\uBB3C \uC5D4\uD2F0\uD2F0(character)").option("--type <type>", "\uC2A4\uD0AC \uC720\uD615(template|instruction|tool)").option("--official <value>", "\uACF5\uC2DD \uC2A4\uD0AC\uB9CC true \uB610\uB294 false").option("--cursor <id>", "\uB2E4\uC74C \uD398\uC774\uC9C0 UUID").option("--all", "\uBAA8\uB4E0 \uD398\uC774\uC9C0 \uC870\uD68C"), async (ctx, _args, opts) => {
4151
5242
  const query = opts.query === void 0 ? void 0 : z9.string().trim().min(1).max(100).safeParse(opts.query);
4152
5243
  if (query && !query.success) throw new CliError("\uAC80\uC0C9\uC5B4\uB294 1~100\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
4153
5244
  const selectedCategory = opts.category === void 0 ? void 0 : category.safeParse(opts.category);
@@ -4172,13 +5263,13 @@ function registerSkillCommands(program2, runtime2) {
4172
5263
  }, contract });
4173
5264
  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);
4174
5265
  if (!opts.all) {
4175
- ctx.output(result);
5266
+ ctx.output({ ...result, data: markCenterOnly(result.data) });
4176
5267
  return;
4177
5268
  }
4178
5269
  contract = result.time_contract;
4179
5270
  all.push(...result.data);
4180
5271
  if (!result.pagination?.has_more) {
4181
- ctx.output({ data: all, time_contract: result.time_contract });
5272
+ ctx.output({ data: markCenterOnly(all), time_contract: result.time_contract });
4182
5273
  return;
4183
5274
  }
4184
5275
  const next = result.pagination.next_cursor;
@@ -4189,44 +5280,91 @@ function registerSkillCommands(program2, runtime2) {
4189
5280
  cursor = next;
4190
5281
  }
4191
5282
  });
4192
- runtime2.action(skill.command("get <slug>"), async (ctx, [value2]) => {
5283
+ runtime2.action(skill.command("get <slug>").description("\uC2A4\uD0AC \uC0C1\uC138 \uC870\uD68C (skill:read)"), async (ctx, [value2]) => {
4193
5284
  const parsed = skillSlug.safeParse(value2);
4194
5285
  if (!parsed.success) throw new CliError("\uC2A4\uD0AC \uC774\uB984\uC744 \uD655\uC778\uD558\uC138\uC694.");
4195
- ctx.output(await ctx.api.request("GET", `/v4/skills/${parsed.data}`));
5286
+ const detail = await ctx.api.request("GET", `/v4/skills/${parsed.data}`);
5287
+ ctx.output({ ...detail, data: markCenterOnly(detail.data) });
4196
5288
  });
4197
- runtime2.action(skill.command("form <slug> <version>"), async (ctx, [value2, version]) => {
5289
+ runtime2.action(skill.command("form <slug> <version>").description("\uC2A4\uD0AC \uBC84\uC804 \uC785\uB825 \uC591\uC2DD \uC870\uD68C (skill:read)"), async (ctx, [value2, version]) => {
4198
5290
  const parsed = skillSlug.safeParse(value2);
4199
5291
  const parsedVersion = semver.safeParse(version);
4200
5292
  if (!parsed.success || !parsedVersion.success) throw new CliError("\uC2A4\uD0AC \uC774\uB984\uACFC \uBC84\uC804\uC744 \uD655\uC778\uD558\uC138\uC694.");
4201
5293
  ctx.output(await ctx.api.request("GET", `/v4/skills/${parsed.data}/versions/${parsedVersion.data}/form`));
4202
5294
  });
4203
- runtime2.action(skill.command("status <skill-id> <version-id>"), async (ctx, [skillId, versionId]) => {
5295
+ runtime2.action(skill.command("status <skill-id> <version-id>").description("\uB0B4 \uC2A4\uD0AC \uBC84\uC804\uC758 \uC2EC\uC0AC\xB7\uAC8C\uC2DC \uC0C1\uD0DC \uC870\uD68C (skill:read)"), async (ctx, [skillId, versionId]) => {
4204
5296
  const ids = skillVersionIds(skillId, versionId);
4205
5297
  ctx.output(await ctx.api.request("GET", `/v4/creator/skills/${ids.skillId}/versions/${ids.versionId}`));
4206
5298
  });
4207
- runtime2.action(skill.command("validate [dir]").option("--remote", "\uC11C\uBC84\uC5D0\uC11C\uB3C4 \uD328\uD0A4\uC9C0\uB97C \uB4DC\uB77C\uC774\uB7F0 \uAC80\uC99D"), async (ctx, [dir], opts) => {
5299
+ runtime2.action(skill.command("validate [dir]").description("\uC2A4\uD0AC \uD328\uD0A4\uC9C0 \uB85C\uCEEC \uAC80\uC0AC (\uB85C\uADF8\uC778 \uBD88\uD544\uC694, --remote\uB294 skill:write)").option("--remote", "\uC11C\uBC84\uC5D0\uC11C\uB3C4 \uD328\uD0A4\uC9C0\uB97C \uB4DC\uB77C\uC774\uB7F0 \uAC80\uC99D (skill:write)"), async (ctx, [dir], opts) => {
4208
5300
  const prepared = await prepareSkillPackage(dir ?? ".");
4209
- if (opts.remote && !ctx.options.dryRun) {
4210
- const remote = await validateSkillRemotely(ctx.api, prepared.bytes);
4211
- ctx.output({
4212
- local_valid: true,
4213
- sha256: prepared.sha256,
4214
- byte_size: prepared.bytes.length,
4215
- file_count: prepared.file_count,
4216
- manifest: prepared.manifest,
4217
- remote
4218
- });
4219
- } else ctx.output({
5301
+ const local = {
4220
5302
  local_valid: true,
4221
5303
  sha256: prepared.sha256,
4222
5304
  byte_size: prepared.bytes.length,
4223
5305
  file_count: prepared.file_count,
4224
5306
  manifest: prepared.manifest,
4225
- ...opts.remote ? { remote_skipped: true } : {}
5307
+ ...editorReport(prepared)
5308
+ };
5309
+ if (opts.remote && !ctx.options.dryRun) {
5310
+ const remote = await validateSkillRemotely(ctx.api, prepared.bytes);
5311
+ ctx.output({ ...local, remote });
5312
+ } else ctx.output({ ...local, ...opts.remote ? { remote_skipped: true } : {} });
5313
+ });
5314
+ 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) => {
5315
+ const source = dir ?? ".";
5316
+ const prepared = await prepareSkillPackage(source, { fillEditorHash: true });
5317
+ const out = resolve8(opts.out ?? `${prepared.manifest.name}-${prepared.manifest.version}.zip`);
5318
+ const writeManifest = Boolean(opts.writeManifest && prepared.filled_manifest);
5319
+ if (!ctx.options.dryRun) {
5320
+ await writeFile2(out, prepared.bytes);
5321
+ if (writeManifest) await writeFile2(join3(resolve8(source), "clack.skill.json"), prepared.filled_manifest);
5322
+ }
5323
+ ctx.output({
5324
+ ...ctx.options.dryRun ? { dry_run: true } : {},
5325
+ out,
5326
+ sha256: prepared.sha256,
5327
+ byte_size: prepared.bytes.length,
5328
+ file_count: prepared.file_count,
5329
+ slug: prepared.manifest.name,
5330
+ version: prepared.manifest.version,
5331
+ ...prepared.filled_manifest ? { editor_hash_filled: true, manifest_written: writeManifest && !ctx.options.dryRun } : {},
5332
+ ...editorReport(prepared)
4226
5333
  });
4227
5334
  });
4228
- runtime2.action(skill.command("push [dir]").option("--skill-id <id>", "\uAE30\uC874 \uC2A4\uD0AC ID\uC5D0 \uC0C8 \uBC84\uC804 \uCD94\uAC00"), async (ctx, [dir], opts) => {
4229
- const prepared = await prepareSkillPackage(dir ?? ".");
5335
+ 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");
5336
+ runtime2.action(
5337
+ 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", `
5338
+ ${SDK_LOCATION_HELP}
5339
+ `),
5340
+ async (ctx, _args, opts) => {
5341
+ const args = [];
5342
+ for (const [flag, value2] of [
5343
+ ["editor", opts.editor],
5344
+ ["document", opts.document],
5345
+ ["schema", opts.schema],
5346
+ ["host-port", opts.hostPort],
5347
+ ["editor-port", opts.editorPort]
5348
+ ]) {
5349
+ if (value2 === void 0) continue;
5350
+ 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.");
5351
+ args.push(`--${flag}`, flag.endsWith("port") ? value2 : resolve8(value2));
5352
+ }
5353
+ const sdkDir = resolveSdkDir(opts.sdkDir);
5354
+ const serve = join3(sdkDir, "mock-host/serve.mjs");
5355
+ if (ctx.options.dryRun) {
5356
+ ctx.output({ dry_run: true, sdk_dir: sdkDir, command: ["node", serve, ...args] });
5357
+ return;
5358
+ }
5359
+ await new Promise((done, fail2) => {
5360
+ const child = spawn(process.execPath, [serve, ...args], { stdio: "inherit" });
5361
+ 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")));
5362
+ 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());
5363
+ });
5364
+ }
5365
+ );
5366
+ 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) => {
5367
+ const prepared = await prepareSkillPackage(dir ?? ".", { fillEditorHash: true });
4230
5368
  if (prepared.manifest.type !== "instruction" && prepared.manifest.type !== "template") {
4231
5369
  throw new CliError("\uD604\uC7AC \uC2A4\uD0AC \uC5C5\uB85C\uB4DC\uB294 instruction\xB7template \uC720\uD615\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.", "SKILL_TYPE_UNSUPPORTED");
4232
5370
  }
@@ -4247,7 +5385,7 @@ function registerSkillCommands(program2, runtime2) {
4247
5385
  }
4248
5386
  ctx.output(await pushSkillVersion(ctx.api, prepared, skillIdOption?.data));
4249
5387
  });
4250
- runtime2.action(skill.command("complete <skill-id> <version-id>"), async (ctx, [skillId, versionId]) => {
5388
+ runtime2.action(skill.command("complete <skill-id> <version-id>").description("\uC5C5\uB85C\uB4DC\uD55C \uC2A4\uD0AC \uBC84\uC804 \uC644\uB8CC \uCC98\uB9AC \uC7AC\uC2DC\uB3C4 (skill:write)"), async (ctx, [skillId, versionId]) => {
4251
5389
  ensurePat(ctx.api);
4252
5390
  const parsedSkillId = uuid2.safeParse(skillId);
4253
5391
  const parsedVersionId = uuid2.safeParse(versionId);
@@ -4258,8 +5396,8 @@ function registerSkillCommands(program2, runtime2) {
4258
5396
  }
4259
5397
  ctx.output(await ctx.api.request("POST", `/v4/creator/skills/${parsedSkillId.data}/versions/${parsedVersionId.data}/complete`));
4260
5398
  });
4261
- runtime2.action(skill.command("submit <skill-id> <version-id>"), async (ctx, [skillId, versionId]) => {
4262
- ensurePat(ctx.api, "\uC2A4\uD0AC \uC2EC\uC0AC \uC81C\uCD9C\uC5D0\uB294 skill:write\uC640 skill:publish \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070(pat_)\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
5399
+ runtime2.action(skill.command("submit <skill-id> <version-id>").description("\uAC80\uC99D \uC644\uB8CC\uD55C \uC2A4\uD0AC \uBC84\uC804 \uC2EC\uC0AC \uC81C\uCD9C (skill:write\uC640 skill:publish \uB458 \uB2E4 \uD544\uC694)"), async (ctx, [skillId, versionId]) => {
5400
+ ensurePat(ctx.api, PAT_FOR_SUBMIT);
4263
5401
  const ids = skillVersionIds(skillId, versionId);
4264
5402
  const path = `/v4/creator/skills/${ids.skillId}/versions/${ids.versionId}`;
4265
5403
  const current = serverData(managedVersionSchema, (await ctx.api.request("GET", path)).data);
@@ -4279,8 +5417,8 @@ function registerSkillCommands(program2, runtime2) {
4279
5417
  await ctx.confirm(`${current.version} \uBC84\uC804(\uD328\uD0A4\uC9C0 ${current.package_hash})\uC744 \uC2EC\uC0AC\uC5D0 \uC81C\uCD9C\uD560\uAE4C\uC694?`);
4280
5418
  ctx.output(await ctx.api.request("POST", `${path}/submit`));
4281
5419
  });
4282
- runtime2.action(skill.command("deprecate <skill-id>").description("\uC2A4\uD0AC\uC744 \uC9C0\uC6D0 \uC885\uB8CC\uD55C\uB2E4 (\uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC74C, \uC0C8 \uC124\uCE58\xB7\uC0C8 \uC81C\uC791 \uBD88\uAC00)"), async (ctx, [skillId]) => {
4283
- ensurePat(ctx.api);
5420
+ runtime2.action(skill.command("deprecate <skill-id>").description("\uC2A4\uD0AC\uC744 \uC9C0\uC6D0 \uC885\uB8CC\uD55C\uB2E4 (\uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC74C, \uC0C8 \uC124\uCE58\xB7\uC0C8 \uC81C\uC791 \uBD88\uAC00, skill:publish)"), async (ctx, [skillId]) => {
5421
+ ensurePat(ctx.api, PAT_FOR_DEPRECATE);
4284
5422
  const parsedSkillId = uuid2.safeParse(skillId);
4285
5423
  if (!parsedSkillId.success) throw new CliError("\uC2A4\uD0AC ID\uB294 UUID\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
4286
5424
  const path = `/v4/creator/skills/${parsedSkillId.data}/deprecate`;
@@ -4291,8 +5429,8 @@ function registerSkillCommands(program2, runtime2) {
4291
5429
  await ctx.confirm("\uC774 \uC2A4\uD0AC\uC744 \uC9C0\uC6D0 \uC885\uB8CC\uD560\uAE4C\uC694? \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC73C\uBA70 \uC0C8 \uC124\uCE58\xB7\uC0C8 \uC81C\uC791\uC774 \uBD88\uAC00\uB2A5\uD569\uB2C8\uB2E4(\uAE30\uC874 \uCF58\uD150\uCE20\uC758 \uACE0\uC815 \uBC84\uC804\uC740 \uC720\uC9C0\uB429\uB2C8\uB2E4).");
4292
5430
  ctx.output(await ctx.api.request("POST", path));
4293
5431
  });
4294
- runtime2.action(skill.command("cancel <skill-id> <version-id>"), async (ctx, [skillId, versionId]) => {
4295
- ensurePat(ctx.api);
5432
+ runtime2.action(skill.command("cancel <skill-id> <version-id>").description("\uC2A4\uD0AC \uBC84\uC804\uC758 \uC5C5\uB85C\uB4DC \uB610\uB294 \uC2EC\uC0AC \uCDE8\uC18C (skill:write)"), async (ctx, [skillId, versionId]) => {
5433
+ ensurePat(ctx.api, PAT_FOR_CANCEL);
4296
5434
  const ids = skillVersionIds(skillId, versionId);
4297
5435
  const path = `/v4/creator/skills/${ids.skillId}/versions/${ids.versionId}`;
4298
5436
  if (ctx.options.dryRun) {
@@ -4303,9 +5441,9 @@ function registerSkillCommands(program2, runtime2) {
4303
5441
  ctx.output(await ctx.api.request("POST", `${path}/cancel`));
4304
5442
  });
4305
5443
  runtime2.action(
4306
- skill.command("release <skill-id> <version-id>").requiredOption("--visibility <value>", "private|unlisted|public"),
5444
+ skill.command("release <skill-id> <version-id>").description("\uC2B9\uC778\uB41C \uC2A4\uD0AC \uBC84\uC804\uC744 \uACF5\uAC1C \uBC94\uC704\uB85C \uAC8C\uC2DC (skill:publish)").requiredOption("--visibility <value>", "private|unlisted|public"),
4307
5445
  async (ctx, [skillId, versionId], opts) => {
4308
- ensurePat(ctx.api);
5446
+ ensurePat(ctx.api, PAT_FOR_RELEASE);
4309
5447
  const ids = skillVersionIds(skillId, versionId);
4310
5448
  const selected = visibility.safeParse(opts.visibility);
4311
5449
  if (!selected.success) throw new CliError("\uACF5\uAC1C \uBC94\uC704\uB294 private, unlisted, public \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
@@ -4330,6 +5468,39 @@ function registerSkillCommands(program2, runtime2) {
4330
5468
  }
4331
5469
  );
4332
5470
  }
5471
+ function markCenterOnly(value2) {
5472
+ const mark2 = (item) => {
5473
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
5474
+ const row = item;
5475
+ return row.center_only === true || row.editor === true ? { ...row, center_only: true, availability: "\uC13C\uD130 \uC804\uC6A9" } : row;
5476
+ };
5477
+ return Array.isArray(value2) ? value2.map(mark2) : mark2(value2);
5478
+ }
5479
+ function editorReport(prepared) {
5480
+ if (!prepared.editor) return {};
5481
+ return {
5482
+ editor: prepared.editor,
5483
+ center_only: true,
5484
+ ...prepared.warnings?.length ? { warnings: prepared.warnings } : {},
5485
+ 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."
5486
+ };
5487
+ }
5488
+ 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.";
5489
+ function resolveSdkDir(option) {
5490
+ const candidates = [];
5491
+ if (option) candidates.push(resolve8(option));
5492
+ else {
5493
+ if (process.env.CLACK_SKILL_EDITOR_SDK_DIR) candidates.push(resolve8(process.env.CLACK_SKILL_EDITOR_SDK_DIR));
5494
+ try {
5495
+ candidates.push(resolve8(createRequire(join3(process.cwd(), "noop.js")).resolve("@clack/skill-editor-sdk/package.json"), ".."));
5496
+ } catch {
5497
+ }
5498
+ if (process.env.CLACK_MONOREPO_DIR) candidates.push(resolve8(process.env.CLACK_MONOREPO_DIR, "clack-skill-editor-sdk"));
5499
+ }
5500
+ const found = candidates.find((dir) => existsSync(join3(dir, "mock-host/serve.mjs")));
5501
+ 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");
5502
+ return found;
5503
+ }
4333
5504
  function skillVersionIds(skillId, versionId) {
4334
5505
  const parsedSkillId = uuid2.safeParse(skillId);
4335
5506
  const parsedVersionId = uuid2.safeParse(versionId);
@@ -4387,7 +5558,7 @@ function parsePat(schema, input, label) {
4387
5558
  return parsed.data;
4388
5559
  }
4389
5560
  var toolDefinitions = [
4390
- { name: "platform_authoring", description: "\uACF5\uAC1C \uC2A4\uD0AC \uC81C\uC791 \uC138\uC158\xB7\uD3FC\xB7\uC790\uC0B0\xB7\uC774\uBBF8\uC9C0 \uC0DD\uC131\xB7\uD328\uD0A4\uC9D5\uC744 \uCC98\uB9AC\uD569\uB2C8\uB2E4. \uC774\uBBF8\uC9C0 \uC0DD\uC131\uC5D0\uB294 \uACAC\uC801 \uC2B9\uC778 \uAC00\uACA9\uACFC \uBA71\uB4F1\uC131 \uD0A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.", inputSchema: { type: "object", ...z10.toJSONSchema(authoringInput) }, annotations: { readOnlyHint: false, destructiveHint: false } },
5561
+ { name: "platform_authoring", description: "\uACF5\uAC1C \uC2A4\uD0AC \uC81C\uC791 \uC138\uC158\xB7\uD3FC\xB7\uC790\uC0B0\xB7\uC774\uBBF8\uC9C0 \uC0DD\uC131\xB7\uD328\uD0A4\uC9D5\uC744 \uCC98\uB9AC\uD569\uB2C8\uB2E4. \uC774\uBBF8\uC9C0 \uC0DD\uC131\uC5D0\uB294 \uACAC\uC801 \uC2B9\uC778 \uAC00\uACA9\uACFC \uBA71\uB4F1\uC131 \uD0A4\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. creator-content:write \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.", inputSchema: { type: "object", ...z10.toJSONSchema(authoringInput) }, annotations: { readOnlyHint: false, destructiveHint: false } },
4391
5562
  {
4392
5563
  name: "platform_usage_get",
4393
5564
  description: "\uC11C\uBC84 \uD0A4\uAC00 \uC18D\uD55C \uCF58\uD150\uCE20\uC758 \uD50C\uB7AB\uD3FC \uC0AC\uC6A9\uB7C9\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
@@ -4433,13 +5604,13 @@ var toolDefinitions = [
4433
5604
  // 아래는 개인 액세스 토큰(CLACK_TOKEN, pat_)으로 동작하는 개인 도구다. 서버 키 도구와 인증 평면이 다르다.
4434
5605
  {
4435
5606
  name: "platform_skill_list",
4436
- description: "\uACF5\uAC1C \uC2A4\uD0AC \uBAA9\uB85D\uC744 \uC774\uB984\uC73C\uB85C \uAC80\uC0C9\uD569\uB2C8\uB2E4.",
5607
+ description: "\uACF5\uAC1C \uC2A4\uD0AC \uBAA9\uB85D\uC744 \uC774\uB984\uC73C\uB85C \uAC80\uC0C9\uD569\uB2C8\uB2E4. skill:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
4437
5608
  inputSchema: z10.toJSONSchema(patSchemas.skillList),
4438
5609
  annotations: { readOnlyHint: true, destructiveHint: false }
4439
5610
  },
4440
5611
  {
4441
5612
  name: "platform_skill_get",
4442
- description: "\uC2A4\uD0AC \uC0C1\uC138\uB97C \uC774\uB984(slug)\uC73C\uB85C \uC870\uD68C\uD569\uB2C8\uB2E4.",
5613
+ description: "\uC2A4\uD0AC \uC0C1\uC138\uB97C \uC774\uB984(slug)\uC73C\uB85C \uC870\uD68C\uD569\uB2C8\uB2E4. skill:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
4443
5614
  inputSchema: z10.toJSONSchema(patSchemas.skillGet),
4444
5615
  annotations: { readOnlyHint: true, destructiveHint: false }
4445
5616
  },
@@ -4451,13 +5622,13 @@ var toolDefinitions = [
4451
5622
  },
4452
5623
  {
4453
5624
  name: "platform_skill_status",
4454
- description: "\uB0B4 \uC2A4\uD0AC \uBC84\uC804\uC758 \uC2EC\uC0AC\xB7\uAC8C\uC2DC \uC0C1\uD0DC\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.",
5625
+ description: "\uB0B4 \uC2A4\uD0AC \uBC84\uC804\uC758 \uC2EC\uC0AC\xB7\uAC8C\uC2DC \uC0C1\uD0DC\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4. skill:read \uAD8C\uD55C\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
4455
5626
  inputSchema: z10.toJSONSchema(patSchemas.skillVersionRef),
4456
5627
  annotations: { readOnlyHint: true, destructiveHint: false }
4457
5628
  },
4458
5629
  {
4459
5630
  name: "platform_skill_submit",
4460
- description: "\uAC80\uC99D \uC644\uB8CC\uD55C \uC2A4\uD0AC \uBC84\uC804\uC744 \uC2EC\uC0AC\uC5D0 \uC81C\uCD9C\uD569\uB2C8\uB2E4. skill:write\xB7skill:publish \uAD8C\uD55C\uACFC confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
5631
+ description: "\uAC80\uC99D \uC644\uB8CC\uD55C \uC2A4\uD0AC \uBC84\uC804\uC744 \uC2EC\uC0AC\uC5D0 \uC81C\uCD9C\uD569\uB2C8\uB2E4. skill:write\uC640 skill:publish \uAD8C\uD55C(\uB458 \uB2E4)\uACFC confirm:true\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
4461
5632
  inputSchema: z10.toJSONSchema(patSchemas.skillSubmit),
4462
5633
  annotations: { readOnlyHint: false, destructiveHint: true }
4463
5634
  },
@@ -4507,7 +5678,7 @@ var toolDefinitions = [
4507
5678
  function error(id2, code, message) {
4508
5679
  return { jsonrpc: "2.0", id: id2, error: { code, message } };
4509
5680
  }
4510
- function isObject2(value2) {
5681
+ function isObject3(value2) {
4511
5682
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
4512
5683
  }
4513
5684
  function toolResult(value2, isError = false) {
@@ -4526,7 +5697,7 @@ var PlatformMcpServer = class {
4526
5697
  env;
4527
5698
  initialized = false;
4528
5699
  async handle(message) {
4529
- 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) {
5700
+ 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) {
4530
5701
  return error(null, -32600, "\uC62C\uBC14\uB978 JSON-RPC \uC694\uCCAD\uC774 \uC544\uB2D9\uB2C8\uB2E4.");
4531
5702
  }
4532
5703
  const request = message;
@@ -4535,7 +5706,7 @@ var PlatformMcpServer = class {
4535
5706
  return void 0;
4536
5707
  }
4537
5708
  if (request.method === "initialize") {
4538
- const requested = isObject2(request.params) && typeof request.params.protocolVersion === "string" ? request.params.protocolVersion : "";
5709
+ const requested = isObject3(request.params) && typeof request.params.protocolVersion === "string" ? request.params.protocolVersion : "";
4539
5710
  if (!requested) return error(request.id, -32602, "\uD504\uB85C\uD1A0\uCF5C \uBC84\uC804\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
4540
5711
  this.initialized = false;
4541
5712
  return { jsonrpc: "2.0", id: request.id, result: {
@@ -4548,7 +5719,7 @@ var PlatformMcpServer = class {
4548
5719
  if (!this.initialized) return error(request.id, -32e3, "MCP \uCD08\uAE30\uD654\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
4549
5720
  if (request.method === "tools/list") return { jsonrpc: "2.0", id: request.id, result: { tools: toolDefinitions } };
4550
5721
  if (request.method !== "tools/call") return error(request.id, -32601, "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 MCP \uBA54\uC11C\uB4DC\uC785\uB2C8\uB2E4.");
4551
- if (!isObject2(request.params) || typeof request.params.name !== "string" || request.params.arguments !== void 0 && !isObject2(request.params.arguments)) {
5722
+ if (!isObject3(request.params) || typeof request.params.name !== "string" || request.params.arguments !== void 0 && !isObject3(request.params.arguments)) {
4552
5723
  return error(request.id, -32602, "\uB3C4\uAD6C \uC774\uB984\uACFC \uC778\uC790\uB97C \uD655\uC778\uD558\uC138\uC694.");
4553
5724
  }
4554
5725
  const name2 = request.params.name;
@@ -4558,7 +5729,7 @@ var PlatformMcpServer = class {
4558
5729
  const writeKind = name2 === "platform_server_data_put" ? "put" : name2 === "platform_server_data_patch" ? "patch" : name2 === "platform_server_data_delete" ? "delete" : null;
4559
5730
  if (writeKind) {
4560
5731
  const preview = platformWritePreview(writeKind, args);
4561
- if (!isObject2(args) || args.confirm !== true) {
5732
+ if (!isObject3(args) || args.confirm !== true) {
4562
5733
  return { jsonrpc: "2.0", id: request.id, result: toolResult({ preview: true, confirmation_required: true, ...preview }) };
4563
5734
  }
4564
5735
  }
@@ -4574,9 +5745,9 @@ var PlatformMcpServer = class {
4574
5745
  return { jsonrpc: "2.0", id: request.id, result: toolResult({ error: failure }, true) };
4575
5746
  }
4576
5747
  }
4577
- /** 개인 액세스 토큰(platform:read·platform:write와 skill:*, creator-content:publish 스코프) 기반 도구. */
5748
+ /** 개인 액세스 토큰(platform:read·platform:write, skill:*, creator-content:write·publish 스코프) 기반 도구. */
4578
5749
  async callPatTool(name2, args) {
4579
- if (patDestructiveToolNames.has(name2) && (!isObject2(args) || args.confirm !== true)) {
5750
+ if (patDestructiveToolNames.has(name2) && (!isObject3(args) || args.confirm !== true)) {
4580
5751
  return { preview: true, confirmation_required: true, tool: name2, arguments: redact(args) };
4581
5752
  }
4582
5753
  const client = platformPatClient(this.settings, this.env);
@@ -4591,7 +5762,7 @@ var PlatformMcpServer = class {
4591
5762
  }
4592
5763
  if (name2 === "platform_skill_push") {
4593
5764
  const { dir, skill_id } = parsePat(patSchemas.skillPush, args, "\uC2A4\uD0AC \uC5C5\uB85C\uB4DC \uC785\uB825");
4594
- const prepared = await prepareSkillPackage(dir ?? ".");
5765
+ const prepared = await prepareSkillPackage(dir ?? ".", { fillEditorHash: true });
4595
5766
  if (prepared.manifest.type !== "instruction" && prepared.manifest.type !== "template") {
4596
5767
  throw new CliError("\uD604\uC7AC \uC2A4\uD0AC \uC5C5\uB85C\uB4DC\uB294 instruction\xB7template \uC720\uD615\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.", "SKILL_TYPE_UNSUPPORTED");
4597
5768
  }
@@ -4655,6 +5826,19 @@ async function servePlatformMcp(settings, env = process.env) {
4655
5826
 
4656
5827
  // src/commands/integrations.ts
4657
5828
  var SKILLS_REPOSITORY = "https://github.com/clack-project/skills";
5829
+ var AGENT_SKILLS = [
5830
+ { name: "clack-setup", audience: "general", minimum_cli_version: "0.1.1", cli_required: true },
5831
+ { name: "clack-products", audience: "general", minimum_cli_version: "0.1.1", cli_required: true },
5832
+ { name: "clack-content", audience: "general", minimum_cli_version: "0.1.1", cli_required: true },
5833
+ { name: "clack-channel", audience: "general", minimum_cli_version: "0.1.1", cli_required: true },
5834
+ { name: "clack-profile", audience: "general", minimum_cli_version: "0.1.1", cli_required: true },
5835
+ { name: "clack-mcp", audience: "general", minimum_cli_version: "0.1.0", cli_required: false },
5836
+ { name: "clack-creator-content", audience: "creator", minimum_cli_version: "0.1.1", cli_required: true },
5837
+ { name: "clack-page", audience: "creator", minimum_cli_version: "0.1.1", cli_required: true },
5838
+ { name: "clack-skill-package", audience: "creator", minimum_cli_version: "0.1.1", cli_required: true },
5839
+ { name: "clack-platform-data", audience: "creator", minimum_cli_version: "0.1.1", cli_required: true }
5840
+ ];
5841
+ var SKILLS_MINIMUM_CLI_VERSION = "0.1.1";
4658
5842
  function quoteArgument(value2) {
4659
5843
  return `'${value2.replaceAll("'", "'\\''")}'`;
4660
5844
  }
@@ -4687,7 +5871,7 @@ function registerIntegrationCommands(program2, runtime2) {
4687
5871
  const mcp = program2.command("mcp").description("MCP \uD074\uB77C\uC774\uC5B8\uD2B8 \uC5F0\uACB0\xB7\uD50C\uB7AB\uD3FC \uC870\uD68C \uB3C4\uAD6C");
4688
5872
  runtime2.action(mcp.command("config").description("\uD1A0\uD070 \uC5C6\uB294 MCP \uC124\uC815 \uCD9C\uB825 (\uD30C\uC77C \uBCC0\uACBD\xB7\uC11C\uBC84 \uC694\uCCAD \uC5C6\uC74C)").option("--platform", "CLACK_SERVER_KEY\uB97C \uC0AC\uC6A9\uD558\uB294 \uB85C\uCEEC \uD50C\uB7AB\uD3FC \uC870\uD68C \uC11C\uBC84 \uC124\uC815").addOption(new Option2("--print", "Claude Code .mcp.json \uC870\uAC01 \uCD9C\uB825 (\uAE30\uBCF8)").conflicts(["claude", "codex"])).addOption(new Option2("--claude", "Claude Code \uB4F1\uB85D \uBA85\uB839 \uCD9C\uB825").conflicts(["print", "codex"])).addOption(new Option2("--codex", "Codex config.toml \uC870\uAC01 \uCD9C\uB825").conflicts(["print", "claude"])), async (ctx, _args, opts) => {
4689
5873
  const config = opts.platform ? platformMcpConfiguration(ctx.api.settings.baseUrl) : mcpConfiguration(ctx.api.settings.baseUrl);
4690
- const instructions = opts.platform ? "\uC5D0\uC774\uC804\uD2B8 \uC2E4\uD589 \uD658\uACBD\uC5D0 \uCF58\uD150\uCE20 \uC11C\uBC84 \uD0A4 \uB3C4\uAD6C\uB294 \uBC1C\uAE09\uBC1B\uC740 \uC11C\uBC84 \uD0A4\uB97C CLACK_SERVER_KEY\uB85C, \uC2A4\uD0AC\xB7\uCF58\uD150\uCE20 \uACF5\uAC1C\xB7\uACF5\uC720 \uBB38\uC11C \uB3C4\uAD6C\uB294 platform:read\xB7platform:write(\uD544\uC694 \uC2DC skill:*\xB7creator-content:publish) \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC744 CLACK_TOKEN\uC73C\uB85C \uC124\uC815\uD558\uC138\uC694. \uD544\uC694\uD55C \uB3C4\uAD6C\uB9CC\uD07C\uB9CC \uC124\uC815\uD558\uBA74 \uB429\uB2C8\uB2E4. \uC124\uC815 \uCD9C\uB825\uC5D0\uB294 \uD0A4\xB7\uD1A0\uD070 \uC6D0\uBB38\uC744 \uB123\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uD658\uACBD\uACFC \uC124\uC815 \uC6D0\uC810\uC774 \uC77C\uCE58\uD558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694." : "\uC571\uC5D0\uC11C \uBC1C\uAE09\uD55C \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC744 \uC5D0\uC774\uC804\uD2B8 \uC2E4\uD589 \uD658\uACBD\uC758 CLACK_TOKEN\uC5D0 \uC548\uC804\uD558\uAC8C \uC124\uC815\uD558\uC138\uC694. CLI\uC5D0 \uC800\uC7A5\uB41C \uD1A0\uD070\uC740 \uC790\uB3D9 \uC804\uB2EC\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uD1A0\uD070\uC744 \uB300\uD654\xB7\uBA85\uB839 \uAE30\uB85D\xB7\uC124\uC815 \uD30C\uC77C\uC5D0 \uBD99\uC5EC \uB123\uC9C0 \uB9C8\uC138\uC694. CLI \uD504\uB85C\uD544\uC744 \uBC14\uAFB8\uC5B4\uB3C4 \uAE30\uC874 MCP \uC124\uC815\uC740 \uAC31\uC2E0\uB418\uC9C0 \uC54A\uC73C\uBBC0\uB85C URL\uACFC \uD1A0\uD070\uC758 \uACC4\uC815\uC744 \uD655\uC778\uD558\uC138\uC694.";
5874
+ const instructions = opts.platform ? "\uC5D0\uC774\uC804\uD2B8 \uC2E4\uD589 \uD658\uACBD\uC5D0 \uCF58\uD150\uCE20 \uC11C\uBC84 \uD0A4 \uB3C4\uAD6C\uB294 \uBC1C\uAE09\uBC1B\uC740 \uC11C\uBC84 \uD0A4\uB97C CLACK_SERVER_KEY\uB85C, \uC2A4\uD0AC\xB7\uC81C\uC791\xB7\uCF58\uD150\uCE20 \uACF5\uAC1C\xB7\uACF5\uC720 \uBB38\uC11C \uB3C4\uAD6C\uB294 platform:read\xB7platform:write(\uD544\uC694 \uC2DC skill:*, \uC81C\uC791\uC740 creator-content:write, \uCF58\uD150\uCE20 \uACF5\uAC1C\uB294 creator-content:publish) \uAD8C\uD55C\uC758 \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC744 CLACK_TOKEN\uC73C\uB85C \uC124\uC815\uD558\uC138\uC694. \uD544\uC694\uD55C \uB3C4\uAD6C\uB9CC\uD07C\uB9CC \uC124\uC815\uD558\uBA74 \uB429\uB2C8\uB2E4. \uC124\uC815 \uCD9C\uB825\uC5D0\uB294 \uD0A4\xB7\uD1A0\uD070 \uC6D0\uBB38\uC744 \uB123\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uD658\uACBD\uACFC \uC124\uC815 \uC6D0\uC810\uC774 \uC77C\uCE58\uD558\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694." : "\uC571\uC5D0\uC11C \uBC1C\uAE09\uD55C \uAC1C\uC778 \uC561\uC138\uC2A4 \uD1A0\uD070\uC744 \uC5D0\uC774\uC804\uD2B8 \uC2E4\uD589 \uD658\uACBD\uC758 CLACK_TOKEN\uC5D0 \uC548\uC804\uD558\uAC8C \uC124\uC815\uD558\uC138\uC694. CLI\uC5D0 \uC800\uC7A5\uB41C \uD1A0\uD070\uC740 \uC790\uB3D9 \uC804\uB2EC\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uD1A0\uD070\uC744 \uB300\uD654\xB7\uBA85\uB839 \uAE30\uB85D\xB7\uC124\uC815 \uD30C\uC77C\uC5D0 \uBD99\uC5EC \uB123\uC9C0 \uB9C8\uC138\uC694. CLI \uD504\uB85C\uD544\uC744 \uBC14\uAFB8\uC5B4\uB3C4 \uAE30\uC874 MCP \uC124\uC815\uC740 \uAC31\uC2E0\uB418\uC9C0 \uC54A\uC73C\uBBC0\uB85C URL\uACFC \uD1A0\uD070\uC758 \uACC4\uC815\uC744 \uD655\uC778\uD558\uC138\uC694.";
4691
5875
  if (ctx.options.json) process.stdout.write(JSON.stringify({ ok: true, data: {
4692
5876
  ...config,
4693
5877
  token_env: opts.platform ? "CLACK_SERVER_KEY" : "CLACK_TOKEN",
@@ -4705,11 +5889,14 @@ function registerIntegrationCommands(program2, runtime2) {
4705
5889
  runtime2.action(program2.command("skills").description("\uC5D0\uC774\uC804\uD2B8 \uC2A4\uD0AC \uC124\uCE58 \uBC29\uBC95 \uC548\uB0B4"), async (ctx) => {
4706
5890
  ctx.output({
4707
5891
  repository: SKILLS_REPOSITORY,
4708
- minimum_cli_version: "0.1.0",
5892
+ minimum_cli_version: SKILLS_MINIMUM_CLI_VERSION,
4709
5893
  cli_version: VERSION,
5894
+ skill_count: AGENT_SKILLS.length,
5895
+ skills: AGENT_SKILLS,
4710
5896
  install: "npx skills add clack-project/skills",
4711
5897
  install_global: "npx skills add clack-project/skills -g",
4712
5898
  install_selected: "npx skills add clack-project/skills -s clack-products",
5899
+ install_selected_multiple: "npx skills add clack-project/skills --skill clack-setup clack-creator-content clack-page",
4713
5900
  update: "npx skills update",
4714
5901
  remove: "npx skills remove",
4715
5902
  message: `\uACF5\uAC1C \uC800\uC7A5\uC18C ${SKILLS_REPOSITORY}\uC5D0\uC11C npx skills\uB85C \uC124\uCE58\uD569\uB2C8\uB2E4. \uC774 \uBA85\uB839\uC740 \uC548\uB0B4\uB9CC \uCD9C\uB825\uD558\uBA70 \uC124\uCE58\uB098 \uB85C\uADF8\uC778\uC744 \uC2E4\uD589\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`
@@ -4725,19 +5912,19 @@ var revision3 = (value2) => parse(z11.coerce.number().int().nonnegative().max(42
4725
5912
  var base = "/v4/creator/custom-pages";
4726
5913
  var presentationSchema = z11.object({
4727
5914
  schema_version: z11.literal(1),
4728
- header_mode: z11.enum(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]),
5915
+ header_mode: z11.enum(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close", "floating_close_safe_area"]),
4729
5916
  color_scheme: z11.enum(["light", "dark"])
4730
5917
  }).strict();
4731
5918
  function registerPageCommands(program2, runtime2) {
4732
5919
  const page = program2.command("page").description("\uC138\uACC4\uAD00\xB7\uB0B4 \uACF5\uAC1C \uD648 HTML \uAFB8\uBBF8\uAE30 (custom-page:read\xB7write\xB7publish)");
4733
- runtime2.action(page.command("list"), async (ctx) => ctx.output(await ctx.api.request("GET", base)));
4734
- runtime2.action(page.command("status <id>"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `${base}/${uuid3(id2)}`)));
4735
- runtime2.action(page.command("create").addOption(new Option3("--target <type>", "\uAFB8\uBC00 \uB300\uC0C1").choices(["profile", "space"]).makeOptionMandatory()).option("--space-id <uuid>", "\uC138\uACC4\uAD00 ID").requiredOption("--policy-version <version>", "\uB3D9\uC758\uD55C \uCF58\uD150\uCE20 \uC81C\uACF5 \uC815\uCC45 \uBC84\uC804"), async (ctx, _args, opts) => {
5920
+ runtime2.action(page.command("list").description("\uB0B4 \uD398\uC774\uC9C0 \uBAA9\uB85D (custom-page:read)"), async (ctx) => ctx.output(await ctx.api.request("GET", base)));
5921
+ runtime2.action(page.command("status <id>").description("\uD398\uC774\uC9C0\uC640 \uBC84\uC804\xB7\uC218\uC815 \uBC88\uD638 \uC0C1\uD0DC (custom-page:read)"), async (ctx, [id2]) => ctx.output(await ctx.api.request("GET", `${base}/${uuid3(id2)}`)));
5922
+ runtime2.action(page.command("create").description("\uAFB8\uBC00 \uD398\uC774\uC9C0 \uB4F1\uB85D (custom-page:write)").addOption(new Option3("--target <type>", "\uAFB8\uBC00 \uB300\uC0C1").choices(["profile", "space"]).makeOptionMandatory()).option("--space-id <uuid>", "\uC138\uACC4\uAD00 ID").requiredOption("--policy-version <version>", "\uB3D9\uC758\uD55C \uCF58\uD150\uCE20 \uC81C\uACF5 \uC815\uCC45 \uBC84\uC804"), async (ctx, _args, opts) => {
4736
5923
  if (opts.target === "profile" && opts.spaceId) throw new CliError("\uB0B4 \uACF5\uAC1C \uD648\uC5D0\uB294 \uC138\uACC4\uAD00 ID\uB97C \uC9C0\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
4737
5924
  const policy = parse(z11.literal("2026-09-22"), opts.policyVersion);
4738
5925
  await mutate(ctx, "POST", base, { target_type: opts.target, ...opts.target === "space" ? { space_id: uuid3(opts.spaceId) } : {}, policy_version: policy });
4739
5926
  });
4740
- runtime2.action(page.command("upload <id> <file>").addOption(new Option3("--header <mode>", "\uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]).default("fixed")).addOption(new Option3("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).default("light")), async (ctx, [id2, file], opts) => {
5927
+ 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) => {
4741
5928
  const path = `${base}/${uuid3(id2)}`;
4742
5929
  const prepared = await prepareContentFile(file);
4743
5930
  const presentation = parse(presentationSchema, { schema_version: 1, header_mode: opts.header, color_scheme: opts.color });
@@ -4754,16 +5941,16 @@ function registerPageCommands(program2, runtime2) {
4754
5941
  throw failure;
4755
5942
  }
4756
5943
  });
4757
- runtime2.action(page.command("complete <id> <upload-id>"), async (ctx, [id2, uploadId]) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/uploads/${uuid3(uploadId)}/complete`, {}));
4758
- runtime2.action(page.command("presentation <id> <version-id>").addOption(new Option3("--header <mode>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]).makeOptionMandatory()).addOption(new Option3("--color <scheme>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).makeOptionMandatory()), async (ctx, [id2, versionId], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/presentation`, {
5944
+ 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`, {}));
5945
+ 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`, {
4759
5946
  presentation: parse(presentationSchema, { schema_version: 1, header_mode: opts.header, color_scheme: opts.color })
4760
5947
  }));
4761
- runtime2.action(page.command("preview <id> <version-id>"), async (ctx, [id2, versionId]) => ctx.output(await ctx.api.request("GET", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/preview`)));
4762
- runtime2.action(page.command("submit <id> <version-id>"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/submit`, {}, "\uC571\uC5D0\uC11C \uD655\uC778\uD55C \uC774 \uBC84\uC804\uC758 \uC2EC\uC0AC\uB97C \uC694\uCCAD\uD560\uAE4C\uC694? \uC2B9\uC778 \uB4A4 \uC9C1\uC811 \uC801\uC6A9\uD574\uC57C \uD569\uB2C8\uB2E4."));
5948
+ 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`)));
5949
+ runtime2.action(page.command("submit <id> <version-id>").description("\uC571\uC5D0\uC11C \uD655\uC778 \uC644\uB8CC\uD55C \uBC84\uC804\uC758 \uC2EC\uC0AC \uC694\uCCAD (custom-page:write\uC640 custom-page:publish \uB458 \uB2E4 \uD544\uC694)"), async (ctx, [id2, versionId]) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/submit`, {}, "\uC571\uC5D0\uC11C \uD655\uC778\uD55C \uC774 \uBC84\uC804\uC758 \uC2EC\uC0AC\uB97C \uC694\uCCAD\uD560\uAE4C\uC694? \uC2B9\uC778 \uB4A4 \uC9C1\uC811 \uC801\uC6A9\uD574\uC57C \uD569\uB2C8\uB2E4."));
4763
5950
  for (const name2 of ["apply", "restore"]) {
4764
- runtime2.action(page.command(`${name2} <id> <version-id>`).requiredOption("--revision <number>", "status\uC5D0\uC11C \uD655\uC778\uD55C \uD604\uC7AC \uC218\uC815 \uBC88\uD638"), async (ctx, [id2, versionId], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/apply`, { version_id: uuid3(versionId), revision: revision3(opts.revision) }, "\uC2B9\uC778\uB41C \uC774 \uBC84\uC804\uC758 \uD30C\uC77C\uACFC \uD5E4\uB354 \uC124\uC815\uC744 \uC801\uC6A9\uD560\uAE4C\uC694?"));
5951
+ runtime2.action(page.command(`${name2} <id> <version-id>`).description(`${name2 === "apply" ? "\uC2B9\uC778\uB41C \uBC84\uC804 \uC801\uC6A9" : "\uC774\uC804 \uC2B9\uC778 \uBC84\uC804\uC73C\uB85C \uBCF5\uC6D0"} (custom-page:publish)`).requiredOption("--revision <number>", "status\uC5D0\uC11C \uD655\uC778\uD55C \uD604\uC7AC \uC218\uC815 \uBC88\uD638"), async (ctx, [id2, versionId], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/apply`, { version_id: uuid3(versionId), revision: revision3(opts.revision) }, "\uC2B9\uC778\uB41C \uC774 \uBC84\uC804\uC758 \uD30C\uC77C\uACFC \uD5E4\uB354 \uC124\uC815\uC744 \uC801\uC6A9\uD560\uAE4C\uC694?"));
4765
5952
  }
4766
- runtime2.action(page.command("disable <id>").requiredOption("--revision <number>", "status\uC5D0\uC11C \uD655\uC778\uD55C \uD604\uC7AC \uC218\uC815 \uBC88\uD638"), async (ctx, [id2], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/disable`, { revision: revision3(opts.revision) }, "\uAE30\uBCF8 \uD654\uBA74\uC73C\uB85C \uC804\uD658\uD560\uAE4C\uC694? \uC5C5\uB85C\uB4DC\uD55C \uBC84\uC804\uC740 \uBCF4\uC874\uB429\uB2C8\uB2E4."));
5953
+ runtime2.action(page.command("disable <id>").description("\uAE30\uBCF8 \uD654\uBA74\uC73C\uB85C \uC804\uD658 (custom-page:publish)").requiredOption("--revision <number>", "status\uC5D0\uC11C \uD655\uC778\uD55C \uD604\uC7AC \uC218\uC815 \uBC88\uD638"), async (ctx, [id2], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/disable`, { revision: revision3(opts.revision) }, "\uAE30\uBCF8 \uD654\uBA74\uC73C\uB85C \uC804\uD658\uD560\uAE4C\uC694? \uC5C5\uB85C\uB4DC\uD55C \uBC84\uC804\uC740 \uBCF4\uC874\uB429\uB2C8\uB2E4."));
4767
5954
  }
4768
5955
 
4769
5956
  // src/cli.ts