@clack-platform/cli 0.1.1 → 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.
- package/README.md +24 -3
- package/dist/cli.js +1178 -131
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -150,7 +150,7 @@ function redact(value2) {
|
|
|
150
150
|
// package.json
|
|
151
151
|
var package_default = {
|
|
152
152
|
name: "@clack-platform/cli",
|
|
153
|
-
version: "0.
|
|
153
|
+
version: "0.2.0",
|
|
154
154
|
description: "\uD074\uB799 \uACC4\uC815\uC758 \uC0C1\uD488\xB7\uCF58\uD150\uCE20\xB7\uD504\uB85C\uD544\uC744 \uAD00\uB9AC\uD558\uB294 CLI",
|
|
155
155
|
type: "module",
|
|
156
156
|
bin: {
|
|
@@ -399,9 +399,9 @@ function documentPath(collection2, key) {
|
|
|
399
399
|
return `/platform/v1/data/${collection2}/docs/${key}`;
|
|
400
400
|
}
|
|
401
401
|
function bodySize(body) {
|
|
402
|
-
const
|
|
403
|
-
if (
|
|
404
|
-
return
|
|
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;
|
|
405
405
|
}
|
|
406
406
|
async function readPlatformBodyFile(file) {
|
|
407
407
|
let raw;
|
|
@@ -598,11 +598,11 @@ async function authoringRequest(client, raw, surface = "cli") {
|
|
|
598
598
|
if (input.action === "upload") {
|
|
599
599
|
const stat3 = await lstat2(input.file);
|
|
600
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.");
|
|
601
|
-
const
|
|
601
|
+
const bytes2 = await readFile2(input.file);
|
|
602
602
|
const body2 = new FormData();
|
|
603
603
|
body2.append("field", input.field);
|
|
604
604
|
if (input.item_index !== void 0) body2.append("item_index", String(input.item_index));
|
|
605
|
-
body2.append("file", new Blob([
|
|
605
|
+
body2.append("file", new Blob([bytes2]), basename(input.file));
|
|
606
606
|
return client.request("POST", `${path}/assets`, { body: body2 });
|
|
607
607
|
}
|
|
608
608
|
if (input.action === "image" || input.action === "image-batch") {
|
|
@@ -1262,9 +1262,9 @@ function isRemoteImage(value2) {
|
|
|
1262
1262
|
return false;
|
|
1263
1263
|
}
|
|
1264
1264
|
}
|
|
1265
|
-
function dimensions(
|
|
1265
|
+
function dimensions(bytes2) {
|
|
1266
1266
|
try {
|
|
1267
|
-
const result = imageSize(
|
|
1267
|
+
const result = imageSize(bytes2);
|
|
1268
1268
|
if (!result.width || !result.height || result.width > 2e4 || result.height > 2e4 || !result.type || !MIME[result.type]) throw new Error();
|
|
1269
1269
|
return { width: result.width, height: result.height, mime: MIME[result.type] };
|
|
1270
1270
|
} catch {
|
|
@@ -1291,26 +1291,26 @@ async function prepareImage(source, baseDir = process.cwd(), measureRemote = fal
|
|
|
1291
1291
|
} finally {
|
|
1292
1292
|
await reader.cancel();
|
|
1293
1293
|
}
|
|
1294
|
-
const
|
|
1294
|
+
const bytes3 = new Uint8Array(size);
|
|
1295
1295
|
let offset = 0;
|
|
1296
1296
|
for (const chunk of chunks) {
|
|
1297
|
-
|
|
1297
|
+
bytes3.set(chunk, offset);
|
|
1298
1298
|
offset += chunk.length;
|
|
1299
1299
|
}
|
|
1300
|
-
return { source, ...dimensions(
|
|
1300
|
+
return { source, ...dimensions(bytes3) };
|
|
1301
1301
|
}
|
|
1302
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.");
|
|
1303
1303
|
const path = resolve(baseDir, source);
|
|
1304
|
-
let
|
|
1304
|
+
let bytes2;
|
|
1305
1305
|
try {
|
|
1306
1306
|
const info = await stat(path);
|
|
1307
1307
|
if (!info.isFile() || info.size === 0 || info.size > MAX_IMAGE_BYTES) throw new Error();
|
|
1308
|
-
|
|
1309
|
-
if (
|
|
1308
|
+
bytes2 = await readFile5(path);
|
|
1309
|
+
if (bytes2.length > MAX_IMAGE_BYTES) throw new Error();
|
|
1310
1310
|
} catch {
|
|
1311
1311
|
throw new CliError(`10MB \uC774\uD558 \uC774\uBBF8\uC9C0 \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${path}`);
|
|
1312
1312
|
}
|
|
1313
|
-
return { source: path, bytes, ...dimensions(
|
|
1313
|
+
return { source: path, bytes: bytes2, ...dimensions(bytes2) };
|
|
1314
1314
|
}
|
|
1315
1315
|
async function uploadPrepared(ctx, image2) {
|
|
1316
1316
|
if (!image2.bytes) return { originalUrl: image2.source };
|
|
@@ -1998,6 +1998,11 @@ import { createInterface } from "readline";
|
|
|
1998
1998
|
import { z as z10 } from "zod";
|
|
1999
1999
|
|
|
2000
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";
|
|
2001
2006
|
import { z as z9 } from "zod";
|
|
2002
2007
|
|
|
2003
2008
|
// src/commands/content.ts
|
|
@@ -2034,15 +2039,15 @@ var CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
2034
2039
|
function isObject(value2) {
|
|
2035
2040
|
return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
|
|
2036
2041
|
}
|
|
2037
|
-
function parseObject(
|
|
2042
|
+
function parseObject(bytes2) {
|
|
2038
2043
|
try {
|
|
2039
|
-
const value2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(
|
|
2044
|
+
const value2 = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes2));
|
|
2040
2045
|
return isObject(value2) ? value2 : null;
|
|
2041
2046
|
} catch {
|
|
2042
2047
|
return null;
|
|
2043
2048
|
}
|
|
2044
2049
|
}
|
|
2045
|
-
function inspectContentPackage(
|
|
2050
|
+
function inspectContentPackage(bytes2) {
|
|
2046
2051
|
const warnings = [];
|
|
2047
2052
|
const paths = /* @__PURE__ */ new Set();
|
|
2048
2053
|
const profileFiles = /* @__PURE__ */ new Set();
|
|
@@ -2061,7 +2066,7 @@ function inspectContentPackage(bytes) {
|
|
|
2061
2066
|
let tooLargeExpanded = false;
|
|
2062
2067
|
let files;
|
|
2063
2068
|
try {
|
|
2064
|
-
files = unzipSync(
|
|
2069
|
+
files = unzipSync(bytes2, { filter(entry) {
|
|
2065
2070
|
entries++;
|
|
2066
2071
|
if (entries > MAX_FILES) {
|
|
2067
2072
|
tooManyFiles = true;
|
|
@@ -2148,27 +2153,27 @@ async function prepareContentFile(source) {
|
|
|
2148
2153
|
const file = resolve6(source);
|
|
2149
2154
|
const extension = extname(file).toLowerCase();
|
|
2150
2155
|
if (![".html", ".htm", ".zip"].includes(extension)) throw new CliError("HTML \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC9C0\uC815\uD558\uC138\uC694.");
|
|
2151
|
-
let
|
|
2156
|
+
let bytes2;
|
|
2152
2157
|
try {
|
|
2153
2158
|
const info = await stat2(file);
|
|
2154
2159
|
if (!info.isFile() || info.size === 0 || info.size > MAX_BYTES) throw new Error();
|
|
2155
|
-
|
|
2156
|
-
if (!
|
|
2160
|
+
bytes2 = await readFile8(file);
|
|
2161
|
+
if (!bytes2.length || bytes2.length > MAX_BYTES) throw new Error();
|
|
2157
2162
|
} catch {
|
|
2158
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.");
|
|
2159
2164
|
}
|
|
2160
2165
|
return {
|
|
2161
|
-
bytes,
|
|
2162
|
-
warnings: extension === ".zip" ? inspectContentPackage(
|
|
2166
|
+
bytes: bytes2,
|
|
2167
|
+
warnings: extension === ".zip" ? inspectContentPackage(bytes2) : [],
|
|
2163
2168
|
metadata: {
|
|
2164
2169
|
filename: basename3(file),
|
|
2165
2170
|
content_type: extension === ".zip" ? "application/zip" : "text/html",
|
|
2166
|
-
byte_size:
|
|
2167
|
-
sha256: createHash2("sha256").update(
|
|
2171
|
+
byte_size: bytes2.length,
|
|
2172
|
+
sha256: createHash2("sha256").update(bytes2).digest("hex")
|
|
2168
2173
|
}
|
|
2169
2174
|
};
|
|
2170
2175
|
}
|
|
2171
|
-
async function putContentFile(session2,
|
|
2176
|
+
async function putContentFile(session2, bytes2, fetcher = fetch) {
|
|
2172
2177
|
uuid(session2.upload_id);
|
|
2173
2178
|
let url;
|
|
2174
2179
|
try {
|
|
@@ -2179,7 +2184,7 @@ async function putContentFile(session2, bytes, fetcher = fetch) {
|
|
|
2179
2184
|
}
|
|
2180
2185
|
const contentType = session2.headers?.["Content-Type"];
|
|
2181
2186
|
const checksum = session2.headers?.["x-amz-checksum-sha256"];
|
|
2182
|
-
if (!["text/html", "application/zip"].includes(contentType ?? "") || checksum !== createHash2("sha256").update(
|
|
2187
|
+
if (!["text/html", "application/zip"].includes(contentType ?? "") || checksum !== createHash2("sha256").update(bytes2).digest("base64")) {
|
|
2183
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);
|
|
2184
2189
|
}
|
|
2185
2190
|
rememberSecret(session2.upload_url);
|
|
@@ -2187,7 +2192,7 @@ async function putContentFile(session2, bytes, fetcher = fetch) {
|
|
|
2187
2192
|
const result = await fetcher(url, {
|
|
2188
2193
|
method: "PUT",
|
|
2189
2194
|
headers: { "Content-Type": contentType, "x-amz-checksum-sha256": checksum },
|
|
2190
|
-
body: new Uint8Array(
|
|
2195
|
+
body: new Uint8Array(bytes2),
|
|
2191
2196
|
redirect: "error",
|
|
2192
2197
|
signal: AbortSignal.timeout(12e4)
|
|
2193
2198
|
});
|
|
@@ -2213,7 +2218,7 @@ function registerContentCommands(program2, runtime2) {
|
|
|
2213
2218
|
const body = parse(createSchema, { title: opts.title, description: opts.description, kind: opts.kind, policy_version: opts.policyVersion });
|
|
2214
2219
|
await mutate(ctx, "POST", "/v4/creator/contents", body);
|
|
2215
2220
|
});
|
|
2216
|
-
runtime2.action(content.command("upload <id> <file>").description("HTML/ZIP \uC5C5\uB85C\uB4DC\uB85C \uC0C8 \uBE44\uACF5\uAC1C \uBC84\uC804 \uC0DD\uC131 (creator-content:write)").addOption(new Option("--header <mode>", "\uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"])).addOption(new Option("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"])), async (ctx, [id2, file], opts) => {
|
|
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) => {
|
|
2217
2222
|
const path = `/v4/creator/contents/${uuid(id2)}`;
|
|
2218
2223
|
const prepared = await prepareContentFile(file);
|
|
2219
2224
|
for (const warning of prepared.warnings) process.stderr.write(`\uD328\uD0A4\uC9C0 \uACBD\uACE0: ${warning}
|
|
@@ -2271,36 +2276,155 @@ function registerContentCommands(program2, runtime2) {
|
|
|
2271
2276
|
}
|
|
2272
2277
|
|
|
2273
2278
|
// src/vendor/clack-api-v4-contracts/authoring-semantics.mjs
|
|
2274
|
-
function
|
|
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) {
|
|
2275
2397
|
const errors = [];
|
|
2276
|
-
const
|
|
2398
|
+
const fail2 = (path, message) => errors.push({ path, message });
|
|
2399
|
+
inspectEditorSemantics(skill, data, fail2);
|
|
2277
2400
|
const tools = skill.authoring?.tools ?? [];
|
|
2278
|
-
if (new Set(tools.map((tool) => tool.tool)).size !== tools.length)
|
|
2279
|
-
for (const tool of tools) if (tool.batch_max > tool.max_calls)
|
|
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.");
|
|
2280
2403
|
const slots = skill.output?.asset_slots ?? [];
|
|
2281
2404
|
const paths = /* @__PURE__ */ new Set();
|
|
2282
2405
|
const fields3 = /* @__PURE__ */ new Set();
|
|
2283
2406
|
for (const slot of slots) {
|
|
2284
|
-
if (fields3.has(slot.field))
|
|
2407
|
+
if (fields3.has(slot.field)) fail2("/output/asset_slots", "\uC2AC\uB86F \uD544\uB4DC\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
2285
2408
|
fields3.add(slot.field);
|
|
2286
|
-
if (slot.multiple && slot.multiple.min > slot.multiple.max)
|
|
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.");
|
|
2287
2410
|
for (let index = 0; index < (slot.multiple?.max ?? 1); index++) {
|
|
2288
2411
|
const path = slot.multiple ? slot.multiple.path.replace("{n}", String(index)) : slot.path;
|
|
2289
|
-
if (paths.has(path))
|
|
2412
|
+
if (paths.has(path)) fail2("/output/asset_slots", "\uC2AC\uB86F \uD30C\uC77C \uACBD\uB85C\uB294 \uC911\uBCF5\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
2290
2413
|
paths.add(path);
|
|
2291
2414
|
}
|
|
2292
2415
|
}
|
|
2416
|
+
inspectIntroSemantics(skill, form, slots, fail2);
|
|
2293
2417
|
if (!form) return errors;
|
|
2294
2418
|
const properties = form.properties ?? {};
|
|
2295
2419
|
const exists = (expression, path) => {
|
|
2296
2420
|
const match = /^([a-z][a-z0-9_-]*)(?:\[(\d+)\])?$/.exec(expression);
|
|
2297
2421
|
if (!match || !Object.hasOwn(properties, match[1])) {
|
|
2298
|
-
|
|
2422
|
+
fail2(path, "\uC120\uC5B8\uD55C \uD3FC \uD544\uB4DC\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
2299
2423
|
return;
|
|
2300
2424
|
}
|
|
2301
2425
|
if (match[2] !== void 0) {
|
|
2302
2426
|
const slot = slots.find((value2) => value2.field === match[1]);
|
|
2303
|
-
if (!slot?.multiple || Number(match[2]) >= slot.multiple.max)
|
|
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.");
|
|
2304
2428
|
}
|
|
2305
2429
|
};
|
|
2306
2430
|
const entity = skill.output?.entity_map;
|
|
@@ -2310,30 +2434,539 @@ function inspectAuthoringSemantics(skill, form) {
|
|
|
2310
2434
|
}
|
|
2311
2435
|
for (const slot of slots) {
|
|
2312
2436
|
const node = properties[slot.field];
|
|
2313
|
-
if (!node || node["x-clack-widget"] !== (slot.multiple ? "image_list" : "image"))
|
|
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.");
|
|
2314
2438
|
}
|
|
2315
2439
|
for (const [name2, node] of Object.entries(properties)) {
|
|
2316
2440
|
if (node["x-clack-widget"] === "content_ref") {
|
|
2317
|
-
if (node.type !== "string" || !node["x-clack-source"])
|
|
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.");
|
|
2318
2442
|
for (const target of Object.keys(node["x-clack-source"]?.import ?? {})) {
|
|
2319
2443
|
exists(target, `/properties/${name2}/x-clack-source/import`);
|
|
2320
|
-
if (properties[target]?.["x-clack-widget"] === "content_ref")
|
|
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.");
|
|
2321
2445
|
}
|
|
2322
|
-
} else if (node["x-clack-source"])
|
|
2323
|
-
if (node["x-clack-widget"] === "image_list" && (node.type !== "array" || node.items?.type !== "object" || !node["x-clack-asset"]))
|
|
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.");
|
|
2324
2448
|
}
|
|
2325
2449
|
return errors;
|
|
2326
2450
|
}
|
|
2327
2451
|
|
|
2328
2452
|
// src/lib/skill-package.ts
|
|
2329
|
-
import { createHash as
|
|
2453
|
+
import { createHash as createHash4 } from "crypto";
|
|
2330
2454
|
import { lstat as lstat4, open as open3, readdir } from "fs/promises";
|
|
2331
2455
|
import { constants } from "fs";
|
|
2332
2456
|
import { basename as basename4, extname as extname2, join as join2, resolve as resolve7 } from "path";
|
|
2333
|
-
import { Ajv2020 } from "ajv/dist/2020.js";
|
|
2457
|
+
import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
|
|
2334
2458
|
import { unzipSync as unzipSync2, zipSync } from "fflate";
|
|
2335
2459
|
import { parseDocument } from "yaml";
|
|
2336
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
|
+
|
|
2337
2970
|
// src/vendor/clack-api-v4-contracts/schemas/clack-skill.v1.schema.json
|
|
2338
2971
|
var clack_skill_v1_schema_default = {
|
|
2339
2972
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -2624,6 +3257,64 @@ var clack_skill_v1_schema_default = {
|
|
|
2624
3257
|
"permissions",
|
|
2625
3258
|
"purpose"
|
|
2626
3259
|
]
|
|
3260
|
+
},
|
|
3261
|
+
editor: {
|
|
3262
|
+
type: "object",
|
|
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.",
|
|
3265
|
+
properties: {
|
|
3266
|
+
bundle: {
|
|
3267
|
+
type: "string",
|
|
3268
|
+
pattern: "^(?!/)(?!.*\\.\\.)[A-Za-z0-9_./-]+$"
|
|
3269
|
+
},
|
|
3270
|
+
sha256: {
|
|
3271
|
+
type: "string",
|
|
3272
|
+
pattern: "^[a-f0-9]{64}$"
|
|
3273
|
+
},
|
|
3274
|
+
permissions: {
|
|
3275
|
+
type: "array",
|
|
3276
|
+
items: {
|
|
3277
|
+
type: "string",
|
|
3278
|
+
enum: [
|
|
3279
|
+
"document.read",
|
|
3280
|
+
"document.write",
|
|
3281
|
+
"assets.read",
|
|
3282
|
+
"assets.pick",
|
|
3283
|
+
"ai.suggest",
|
|
3284
|
+
"tools.image.generate",
|
|
3285
|
+
"preview",
|
|
3286
|
+
"package"
|
|
3287
|
+
]
|
|
3288
|
+
},
|
|
3289
|
+
minItems: 2,
|
|
3290
|
+
uniqueItems: true
|
|
3291
|
+
},
|
|
3292
|
+
purpose: {
|
|
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
|
+
]
|
|
3310
|
+
}
|
|
3311
|
+
},
|
|
3312
|
+
required: [
|
|
3313
|
+
"bundle",
|
|
3314
|
+
"sha256",
|
|
3315
|
+
"permissions",
|
|
3316
|
+
"purpose"
|
|
3317
|
+
]
|
|
2627
3318
|
}
|
|
2628
3319
|
},
|
|
2629
3320
|
required: [
|
|
@@ -2885,7 +3576,8 @@ var clack_skill_v1_schema_default = {
|
|
|
2885
3576
|
"fixed",
|
|
2886
3577
|
"scroll_hide",
|
|
2887
3578
|
"translucent_scroll_hide",
|
|
2888
|
-
"floating_close"
|
|
3579
|
+
"floating_close",
|
|
3580
|
+
"floating_close_safe_area"
|
|
2889
3581
|
]
|
|
2890
3582
|
},
|
|
2891
3583
|
color_scheme: {
|
|
@@ -2900,6 +3592,89 @@ var clack_skill_v1_schema_default = {
|
|
|
2900
3592
|
"header_mode",
|
|
2901
3593
|
"color_scheme"
|
|
2902
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
|
+
}
|
|
2903
3678
|
}
|
|
2904
3679
|
},
|
|
2905
3680
|
required: [
|
|
@@ -3042,8 +3817,27 @@ var clack_skill_v1_schema_default = {
|
|
|
3042
3817
|
],
|
|
3043
3818
|
properties: {
|
|
3044
3819
|
authoring: {
|
|
3045
|
-
|
|
3046
|
-
|
|
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
|
+
}
|
|
3047
3841
|
],
|
|
3048
3842
|
properties: {
|
|
3049
3843
|
modes: {
|
|
@@ -3094,6 +3888,136 @@ var clack_skill_v1_schema_default = {
|
|
|
3094
3888
|
"runtime"
|
|
3095
3889
|
]
|
|
3096
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
|
+
}
|
|
3097
4021
|
}
|
|
3098
4022
|
]
|
|
3099
4023
|
};
|
|
@@ -3977,39 +4901,39 @@ var clack_content_v1_schema_default = {
|
|
|
3977
4901
|
var MAX_ZIP = 10 * 1024 * 1024;
|
|
3978
4902
|
var MAX_EXPANDED = 30 * 1024 * 1024;
|
|
3979
4903
|
var MAX_FILES2 = 500;
|
|
3980
|
-
var
|
|
3981
|
-
var ajv = new
|
|
4904
|
+
var textDecoder2 = new TextDecoder("utf-8", { fatal: true });
|
|
4905
|
+
var ajv = new Ajv20202({ allErrors: true, strict: false });
|
|
3982
4906
|
ajv.addSchema(clack_content_v1_schema_default);
|
|
3983
4907
|
var validateManifest = ajv.compile(clack_skill_v1_schema_default);
|
|
3984
|
-
var validateForm = new
|
|
3985
|
-
function
|
|
4908
|
+
var validateForm = new Ajv20202({ allErrors: true, strict: false }).compile(authoring_form_v1_schema_default);
|
|
4909
|
+
function invalid2(message) {
|
|
3986
4910
|
throw new CliError(message, "SKILL_PACKAGE_INVALID");
|
|
3987
4911
|
}
|
|
3988
|
-
function hash(
|
|
3989
|
-
return
|
|
4912
|
+
function hash(bytes2) {
|
|
4913
|
+
return createHash4("sha256").update(bytes2).digest("hex");
|
|
3990
4914
|
}
|
|
3991
4915
|
function object(value2) {
|
|
3992
4916
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
3993
4917
|
}
|
|
3994
|
-
function json(
|
|
3995
|
-
if (
|
|
4918
|
+
function json(bytes2, max, label) {
|
|
4919
|
+
if (bytes2.length > max) invalid2(`${label} \uD06C\uAE30 \uC81C\uD55C\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.`);
|
|
3996
4920
|
try {
|
|
3997
|
-
const parsed = JSON.parse(
|
|
4921
|
+
const parsed = JSON.parse(textDecoder2.decode(bytes2));
|
|
3998
4922
|
if (object(parsed)) return parsed;
|
|
3999
4923
|
} catch {
|
|
4000
4924
|
}
|
|
4001
|
-
return
|
|
4925
|
+
return invalid2(`${label}\uC740 UTF-8 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.`);
|
|
4002
4926
|
}
|
|
4003
4927
|
async function readRegular(path) {
|
|
4004
4928
|
let handle;
|
|
4005
4929
|
try {
|
|
4006
4930
|
handle = await open3(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
4007
4931
|
const info = await handle.stat();
|
|
4008
|
-
if (!info.isFile())
|
|
4932
|
+
if (!info.isFile()) invalid2("\uC2A4\uD0AC \uD328\uD0A4\uC9C0\uC5D0\uB294 \uC77C\uBC18 \uD30C\uC77C\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.");
|
|
4009
4933
|
return await handle.readFile();
|
|
4010
4934
|
} catch (error2) {
|
|
4011
4935
|
if (error2 instanceof CliError) throw error2;
|
|
4012
|
-
return
|
|
4936
|
+
return invalid2("\uC2A4\uD0AC \uD30C\uC77C\uC744 \uC548\uC804\uD558\uAC8C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
4013
4937
|
} finally {
|
|
4014
4938
|
await handle?.close();
|
|
4015
4939
|
}
|
|
@@ -4028,76 +4952,76 @@ async function collectFiles(directory) {
|
|
|
4028
4952
|
const entries = await readdir(join2(directory, relative), { withFileTypes: true });
|
|
4029
4953
|
for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
|
|
4030
4954
|
const path = relative ? `${relative}/${entry.name}` : entry.name;
|
|
4031
|
-
if (!skillPathAllowed(path))
|
|
4032
|
-
if (entry.isSymbolicLink() || !(entry.isDirectory() || entry.isFile()))
|
|
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}`);
|
|
4033
4957
|
if (entry.isDirectory()) {
|
|
4034
4958
|
await walk(path);
|
|
4035
4959
|
continue;
|
|
4036
4960
|
}
|
|
4037
|
-
if (Object.keys(files).length >= MAX_FILES2)
|
|
4961
|
+
if (Object.keys(files).length >= MAX_FILES2) invalid2("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
4038
4962
|
const data = await readRegular(join2(directory, path));
|
|
4039
4963
|
totalBytes += data.length;
|
|
4040
|
-
if (totalBytes > MAX_EXPANDED)
|
|
4964
|
+
if (totalBytes > MAX_EXPANDED) invalid2("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
4041
4965
|
files[path] = data;
|
|
4042
4966
|
}
|
|
4043
4967
|
}
|
|
4044
4968
|
await walk("");
|
|
4045
4969
|
return files;
|
|
4046
4970
|
}
|
|
4047
|
-
function inspectZip(
|
|
4048
|
-
if (!
|
|
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.");
|
|
4049
4973
|
const seen = /* @__PURE__ */ new Set();
|
|
4050
4974
|
let entries = 0;
|
|
4051
4975
|
let totalBytes = 0;
|
|
4052
4976
|
let extracted;
|
|
4053
4977
|
try {
|
|
4054
|
-
extracted = unzipSync2(
|
|
4978
|
+
extracted = unzipSync2(bytes2, { filter(entry) {
|
|
4055
4979
|
entries++;
|
|
4056
|
-
if (entries > MAX_FILES2)
|
|
4980
|
+
if (entries > MAX_FILES2) invalid2("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
4057
4981
|
const path = entry.name.endsWith("/") ? entry.name.slice(0, -1) : entry.name;
|
|
4058
|
-
if (!skillPathAllowed(path) || seen.has(path.toLowerCase()))
|
|
4982
|
+
if (!skillPathAllowed(path) || seen.has(path.toLowerCase())) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${path}`);
|
|
4059
4983
|
seen.add(path.toLowerCase());
|
|
4060
4984
|
if (entry.name.endsWith("/")) return false;
|
|
4061
4985
|
totalBytes += entry.originalSize;
|
|
4062
|
-
if (totalBytes > MAX_EXPANDED)
|
|
4986
|
+
if (totalBytes > MAX_EXPANDED) invalid2("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
4063
4987
|
return true;
|
|
4064
4988
|
} });
|
|
4065
4989
|
} catch (error2) {
|
|
4066
4990
|
if (error2 instanceof CliError) throw error2;
|
|
4067
|
-
return
|
|
4991
|
+
return invalid2("ZIP \uAD6C\uC870\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
4068
4992
|
}
|
|
4069
4993
|
const markdown = extracted["SKILL.md"];
|
|
4070
4994
|
const manifestBytes = extracted["clack.skill.json"];
|
|
4071
|
-
if (!markdown || !manifestBytes)
|
|
4072
|
-
if (markdown.length > 20 * 1024)
|
|
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.");
|
|
4073
4997
|
let frontmatter;
|
|
4074
4998
|
try {
|
|
4075
|
-
const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(
|
|
4076
|
-
if (!match)
|
|
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.");
|
|
4077
5001
|
frontmatter = match[1];
|
|
4078
5002
|
} catch {
|
|
4079
|
-
return
|
|
5003
|
+
return invalid2("SKILL.md\uB294 UTF-8 \uD14D\uC2A4\uD2B8\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
4080
5004
|
}
|
|
4081
5005
|
const document = parseDocument(frontmatter, { version: "1.2", uniqueKeys: true, merge: false });
|
|
4082
5006
|
if (document.errors.length || document.warnings.length || /(?:^|\n)\s*allowed-tools\s*:/i.test(frontmatter)) {
|
|
4083
|
-
|
|
5007
|
+
invalid2("SKILL.md frontmatter\uC5D0 \uC798\uBABB\uB41C \uAC12 \uB610\uB294 allowed-tools\uAC00 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
4084
5008
|
}
|
|
4085
5009
|
let front;
|
|
4086
5010
|
try {
|
|
4087
5011
|
front = document.toJS({ maxAliasCount: 0 });
|
|
4088
5012
|
} catch {
|
|
4089
|
-
return
|
|
5013
|
+
return invalid2("SKILL.md frontmatter\uB97C \uD655\uC778\uD558\uC138\uC694.");
|
|
4090
5014
|
}
|
|
4091
|
-
if (!object(front) || typeof front.name !== "string" || typeof front.description !== "string" || !front.description.length || front.description.length > 1024)
|
|
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.");
|
|
4092
5016
|
const manifest = json(manifestBytes, 64 * 1024, "clack.skill.json");
|
|
4093
|
-
if (!validateManifest(manifest))
|
|
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.");
|
|
4094
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) {
|
|
4095
|
-
|
|
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.");
|
|
4096
5020
|
}
|
|
4097
5021
|
const file = (path, max) => {
|
|
4098
|
-
if (typeof path !== "string" || !skillPathAllowed(path) || !extracted[path])
|
|
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.");
|
|
4099
5023
|
const data = extracted[path];
|
|
4100
|
-
if (max && data.length > max)
|
|
5024
|
+
if (max && data.length > max) invalid2("\uC120\uC5B8\uD55C \uD30C\uC77C\uC758 \uD06C\uAE30 \uC81C\uD55C\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
4101
5025
|
return data;
|
|
4102
5026
|
};
|
|
4103
5027
|
const display = manifest.display;
|
|
@@ -4106,50 +5030,93 @@ function inspectZip(bytes, expectedDirectory) {
|
|
|
4106
5030
|
const authoring = manifest.authoring;
|
|
4107
5031
|
const output2 = manifest.output;
|
|
4108
5032
|
for (const reference of authoring.references ?? []) file(reference, 64 * 1024);
|
|
4109
|
-
|
|
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") {
|
|
4110
5040
|
const form = json(file(authoring.form, 64 * 1024), 64 * 1024, "\uC81C\uC791 \uD3FC");
|
|
4111
|
-
if (!validateForm(form))
|
|
5041
|
+
if (!validateForm(form)) invalid2("\uC81C\uC791 \uD3FC\uC774 \uACF5\uAC1C \uC2A4\uD0A4\uB9C8\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
4112
5042
|
const semanticErrors = inspectAuthoringSemantics(manifest, form);
|
|
4113
|
-
if (semanticErrors.length)
|
|
5043
|
+
if (semanticErrors.length) invalid2(semanticErrors.map((error2) => `${error2.path}: ${error2.message}`).join(" / "));
|
|
4114
5044
|
const template = output2.template;
|
|
4115
|
-
if (template.root !== "runtime" || typeof template.entry !== "string")
|
|
5045
|
+
if (template.root !== "runtime" || typeof template.entry !== "string") invalid2("runtime \uD15C\uD50C\uB9BF \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.");
|
|
4116
5046
|
file(`runtime/${template.entry}`);
|
|
4117
5047
|
const runtime2 = manifest.runtime;
|
|
4118
5048
|
for (const profile of runtime2.profiles) file(`runtime/.clack/profiles/${profile}.yaml`, 16 * 1024);
|
|
4119
5049
|
const examples = Object.keys(extracted).filter((path) => /^examples\/[^/]+\.json$/.test(path));
|
|
4120
|
-
if (!examples.length || examples.length > 10)
|
|
5050
|
+
if (!examples.length || examples.length > 10) invalid2("\uD15C\uD50C\uB9BF \uC608\uC2DC\uB294 1~10\uAC1C \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
4121
5051
|
let validateExample;
|
|
4122
5052
|
try {
|
|
4123
|
-
const examplesAjv = new
|
|
5053
|
+
const examplesAjv = new Ajv20202({ allErrors: true, strict: false });
|
|
4124
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);
|
|
4125
5055
|
validateExample = examplesAjv.compile(form);
|
|
4126
5056
|
} catch {
|
|
4127
|
-
return
|
|
5057
|
+
return invalid2("\uC81C\uC791 \uD3FC JSON Schema\uB97C \uD655\uC778\uD558\uC138\uC694.");
|
|
4128
5058
|
}
|
|
4129
5059
|
for (const path of examples) if (!validateExample(json(file(path, 64 * 1024), 64 * 1024, "\uC608\uC2DC"))) {
|
|
4130
|
-
|
|
5060
|
+
invalid2("\uC81C\uC791 \uD3FC\uACFC \uC608\uC2DC \uB370\uC774\uD130\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
4131
5061
|
}
|
|
4132
5062
|
}
|
|
4133
|
-
return {
|
|
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);
|
|
4134
5089
|
}
|
|
4135
|
-
async function prepareSkillPackage(source) {
|
|
5090
|
+
async function prepareSkillPackage(source, options2 = {}) {
|
|
4136
5091
|
const path = resolve7(source);
|
|
4137
5092
|
let info;
|
|
4138
5093
|
try {
|
|
4139
5094
|
info = await lstat4(path);
|
|
4140
5095
|
} catch {
|
|
4141
|
-
return
|
|
5096
|
+
return invalid2("\uC2A4\uD0AC \uB514\uB809\uD130\uB9AC \uB610\uB294 ZIP \uD30C\uC77C\uC744 \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
4142
5097
|
}
|
|
4143
|
-
if (info.isSymbolicLink())
|
|
4144
|
-
let
|
|
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;
|
|
4145
5101
|
if (info.isDirectory()) {
|
|
4146
5102
|
const files = await collectFiles(path);
|
|
4147
|
-
if (!Object.keys(files).length)
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
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 } : {} };
|
|
4153
5120
|
}
|
|
4154
5121
|
|
|
4155
5122
|
// src/commands/skill.ts
|
|
@@ -4192,7 +5159,7 @@ function ensurePat(client, message = PAT_FOR_WRITE) {
|
|
|
4192
5159
|
}
|
|
4193
5160
|
return token;
|
|
4194
5161
|
}
|
|
4195
|
-
async function validateSkillRemotely(client,
|
|
5162
|
+
async function validateSkillRemotely(client, bytes2) {
|
|
4196
5163
|
const token = ensurePat(client);
|
|
4197
5164
|
const url = new URL("/v4/creator/skills/validate", client.settings.baseUrl);
|
|
4198
5165
|
let response;
|
|
@@ -4210,7 +5177,7 @@ async function validateSkillRemotely(client, bytes) {
|
|
|
4210
5177
|
"X-CLACK-Time-Contract": "utc-v1",
|
|
4211
5178
|
"Cache-Control": "no-store"
|
|
4212
5179
|
},
|
|
4213
|
-
body: new Uint8Array(
|
|
5180
|
+
body: new Uint8Array(bytes2),
|
|
4214
5181
|
redirect: "error",
|
|
4215
5182
|
signal: AbortSignal.timeout(3e4)
|
|
4216
5183
|
});
|
|
@@ -4296,13 +5263,13 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4296
5263
|
}, contract });
|
|
4297
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);
|
|
4298
5265
|
if (!opts.all) {
|
|
4299
|
-
ctx.output(result);
|
|
5266
|
+
ctx.output({ ...result, data: markCenterOnly(result.data) });
|
|
4300
5267
|
return;
|
|
4301
5268
|
}
|
|
4302
5269
|
contract = result.time_contract;
|
|
4303
5270
|
all.push(...result.data);
|
|
4304
5271
|
if (!result.pagination?.has_more) {
|
|
4305
|
-
ctx.output({ data: all, time_contract: result.time_contract });
|
|
5272
|
+
ctx.output({ data: markCenterOnly(all), time_contract: result.time_contract });
|
|
4306
5273
|
return;
|
|
4307
5274
|
}
|
|
4308
5275
|
const next = result.pagination.next_cursor;
|
|
@@ -4316,7 +5283,8 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4316
5283
|
runtime2.action(skill.command("get <slug>").description("\uC2A4\uD0AC \uC0C1\uC138 \uC870\uD68C (skill:read)"), async (ctx, [value2]) => {
|
|
4317
5284
|
const parsed = skillSlug.safeParse(value2);
|
|
4318
5285
|
if (!parsed.success) throw new CliError("\uC2A4\uD0AC \uC774\uB984\uC744 \uD655\uC778\uD558\uC138\uC694.");
|
|
4319
|
-
|
|
5286
|
+
const detail = await ctx.api.request("GET", `/v4/skills/${parsed.data}`);
|
|
5287
|
+
ctx.output({ ...detail, data: markCenterOnly(detail.data) });
|
|
4320
5288
|
});
|
|
4321
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]) => {
|
|
4322
5290
|
const parsed = skillSlug.safeParse(value2);
|
|
@@ -4330,27 +5298,73 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4330
5298
|
});
|
|
4331
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) => {
|
|
4332
5300
|
const prepared = await prepareSkillPackage(dir ?? ".");
|
|
4333
|
-
|
|
4334
|
-
const remote = await validateSkillRemotely(ctx.api, prepared.bytes);
|
|
4335
|
-
ctx.output({
|
|
4336
|
-
local_valid: true,
|
|
4337
|
-
sha256: prepared.sha256,
|
|
4338
|
-
byte_size: prepared.bytes.length,
|
|
4339
|
-
file_count: prepared.file_count,
|
|
4340
|
-
manifest: prepared.manifest,
|
|
4341
|
-
remote
|
|
4342
|
-
});
|
|
4343
|
-
} else ctx.output({
|
|
5301
|
+
const local = {
|
|
4344
5302
|
local_valid: true,
|
|
4345
5303
|
sha256: prepared.sha256,
|
|
4346
5304
|
byte_size: prepared.bytes.length,
|
|
4347
5305
|
file_count: prepared.file_count,
|
|
4348
5306
|
manifest: prepared.manifest,
|
|
4349
|
-
...
|
|
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)
|
|
4350
5333
|
});
|
|
4351
5334
|
});
|
|
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
|
+
);
|
|
4352
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) => {
|
|
4353
|
-
const prepared = await prepareSkillPackage(dir ?? ".");
|
|
5367
|
+
const prepared = await prepareSkillPackage(dir ?? ".", { fillEditorHash: true });
|
|
4354
5368
|
if (prepared.manifest.type !== "instruction" && prepared.manifest.type !== "template") {
|
|
4355
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");
|
|
4356
5370
|
}
|
|
@@ -4454,6 +5468,39 @@ function registerSkillCommands(program2, runtime2) {
|
|
|
4454
5468
|
}
|
|
4455
5469
|
);
|
|
4456
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
|
+
}
|
|
4457
5504
|
function skillVersionIds(skillId, versionId) {
|
|
4458
5505
|
const parsedSkillId = uuid2.safeParse(skillId);
|
|
4459
5506
|
const parsedVersionId = uuid2.safeParse(versionId);
|
|
@@ -4631,7 +5678,7 @@ var toolDefinitions = [
|
|
|
4631
5678
|
function error(id2, code, message) {
|
|
4632
5679
|
return { jsonrpc: "2.0", id: id2, error: { code, message } };
|
|
4633
5680
|
}
|
|
4634
|
-
function
|
|
5681
|
+
function isObject3(value2) {
|
|
4635
5682
|
return value2 !== null && typeof value2 === "object" && !Array.isArray(value2);
|
|
4636
5683
|
}
|
|
4637
5684
|
function toolResult(value2, isError = false) {
|
|
@@ -4650,7 +5697,7 @@ var PlatformMcpServer = class {
|
|
|
4650
5697
|
env;
|
|
4651
5698
|
initialized = false;
|
|
4652
5699
|
async handle(message) {
|
|
4653
|
-
if (!
|
|
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) {
|
|
4654
5701
|
return error(null, -32600, "\uC62C\uBC14\uB978 JSON-RPC \uC694\uCCAD\uC774 \uC544\uB2D9\uB2C8\uB2E4.");
|
|
4655
5702
|
}
|
|
4656
5703
|
const request = message;
|
|
@@ -4659,7 +5706,7 @@ var PlatformMcpServer = class {
|
|
|
4659
5706
|
return void 0;
|
|
4660
5707
|
}
|
|
4661
5708
|
if (request.method === "initialize") {
|
|
4662
|
-
const requested =
|
|
5709
|
+
const requested = isObject3(request.params) && typeof request.params.protocolVersion === "string" ? request.params.protocolVersion : "";
|
|
4663
5710
|
if (!requested) return error(request.id, -32602, "\uD504\uB85C\uD1A0\uCF5C \uBC84\uC804\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
4664
5711
|
this.initialized = false;
|
|
4665
5712
|
return { jsonrpc: "2.0", id: request.id, result: {
|
|
@@ -4672,7 +5719,7 @@ var PlatformMcpServer = class {
|
|
|
4672
5719
|
if (!this.initialized) return error(request.id, -32e3, "MCP \uCD08\uAE30\uD654\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
4673
5720
|
if (request.method === "tools/list") return { jsonrpc: "2.0", id: request.id, result: { tools: toolDefinitions } };
|
|
4674
5721
|
if (request.method !== "tools/call") return error(request.id, -32601, "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 MCP \uBA54\uC11C\uB4DC\uC785\uB2C8\uB2E4.");
|
|
4675
|
-
if (!
|
|
5722
|
+
if (!isObject3(request.params) || typeof request.params.name !== "string" || request.params.arguments !== void 0 && !isObject3(request.params.arguments)) {
|
|
4676
5723
|
return error(request.id, -32602, "\uB3C4\uAD6C \uC774\uB984\uACFC \uC778\uC790\uB97C \uD655\uC778\uD558\uC138\uC694.");
|
|
4677
5724
|
}
|
|
4678
5725
|
const name2 = request.params.name;
|
|
@@ -4682,7 +5729,7 @@ var PlatformMcpServer = class {
|
|
|
4682
5729
|
const writeKind = name2 === "platform_server_data_put" ? "put" : name2 === "platform_server_data_patch" ? "patch" : name2 === "platform_server_data_delete" ? "delete" : null;
|
|
4683
5730
|
if (writeKind) {
|
|
4684
5731
|
const preview = platformWritePreview(writeKind, args);
|
|
4685
|
-
if (!
|
|
5732
|
+
if (!isObject3(args) || args.confirm !== true) {
|
|
4686
5733
|
return { jsonrpc: "2.0", id: request.id, result: toolResult({ preview: true, confirmation_required: true, ...preview }) };
|
|
4687
5734
|
}
|
|
4688
5735
|
}
|
|
@@ -4700,7 +5747,7 @@ var PlatformMcpServer = class {
|
|
|
4700
5747
|
}
|
|
4701
5748
|
/** 개인 액세스 토큰(platform:read·platform:write, skill:*, creator-content:write·publish 스코프) 기반 도구. */
|
|
4702
5749
|
async callPatTool(name2, args) {
|
|
4703
|
-
if (patDestructiveToolNames.has(name2) && (!
|
|
5750
|
+
if (patDestructiveToolNames.has(name2) && (!isObject3(args) || args.confirm !== true)) {
|
|
4704
5751
|
return { preview: true, confirmation_required: true, tool: name2, arguments: redact(args) };
|
|
4705
5752
|
}
|
|
4706
5753
|
const client = platformPatClient(this.settings, this.env);
|
|
@@ -4715,7 +5762,7 @@ var PlatformMcpServer = class {
|
|
|
4715
5762
|
}
|
|
4716
5763
|
if (name2 === "platform_skill_push") {
|
|
4717
5764
|
const { dir, skill_id } = parsePat(patSchemas.skillPush, args, "\uC2A4\uD0AC \uC5C5\uB85C\uB4DC \uC785\uB825");
|
|
4718
|
-
const prepared = await prepareSkillPackage(dir ?? ".");
|
|
5765
|
+
const prepared = await prepareSkillPackage(dir ?? ".", { fillEditorHash: true });
|
|
4719
5766
|
if (prepared.manifest.type !== "instruction" && prepared.manifest.type !== "template") {
|
|
4720
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");
|
|
4721
5768
|
}
|
|
@@ -4865,7 +5912,7 @@ var revision3 = (value2) => parse(z11.coerce.number().int().nonnegative().max(42
|
|
|
4865
5912
|
var base = "/v4/creator/custom-pages";
|
|
4866
5913
|
var presentationSchema = z11.object({
|
|
4867
5914
|
schema_version: z11.literal(1),
|
|
4868
|
-
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"]),
|
|
4869
5916
|
color_scheme: z11.enum(["light", "dark"])
|
|
4870
5917
|
}).strict();
|
|
4871
5918
|
function registerPageCommands(program2, runtime2) {
|
|
@@ -4877,7 +5924,7 @@ function registerPageCommands(program2, runtime2) {
|
|
|
4877
5924
|
const policy = parse(z11.literal("2026-09-22"), opts.policyVersion);
|
|
4878
5925
|
await mutate(ctx, "POST", base, { target_type: opts.target, ...opts.target === "space" ? { space_id: uuid3(opts.spaceId) } : {}, policy_version: policy });
|
|
4879
5926
|
});
|
|
4880
|
-
runtime2.action(page.command("upload <id> <file>").description("HTML/ZIP \uC5C5\uB85C\uB4DC\uB85C \uC0C8 \uBE44\uACF5\uAC1C \uBC84\uC804 \uC0DD\uC131 (custom-page:write)").addOption(new Option3("--header <mode>", "\uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]).default("fixed")).addOption(new Option3("--color <scheme>", "\uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).default("light")), async (ctx, [id2, file], opts) => {
|
|
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) => {
|
|
4881
5928
|
const path = `${base}/${uuid3(id2)}`;
|
|
4882
5929
|
const prepared = await prepareContentFile(file);
|
|
4883
5930
|
const presentation = parse(presentationSchema, { schema_version: 1, header_mode: opts.header, color_scheme: opts.color });
|
|
@@ -4895,7 +5942,7 @@ function registerPageCommands(program2, runtime2) {
|
|
|
4895
5942
|
}
|
|
4896
5943
|
});
|
|
4897
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`, {}));
|
|
4898
|
-
runtime2.action(page.command("presentation <id> <version-id>").description("\uD30C\uC77C\uC740 \uB450\uACE0 \uD5E4\uB354 \uC124\uC815\uB9CC \uBC14\uAFBC \uC0C8 \uBC84\uC804 \uC0DD\uC131 (custom-page:write)").addOption(new Option3("--header <mode>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uD45C\uC2DC").choices(["fixed", "scroll_hide", "translucent_scroll_hide", "floating_close"]).makeOptionMandatory()).addOption(new Option3("--color <scheme>", "\uC0C8 \uBC84\uC804\uC758 \uD5E4\uB354 \uC0C9\uC0C1").choices(["light", "dark"]).makeOptionMandatory()), async (ctx, [id2, versionId], opts) => mutate(ctx, "POST", `${base}/${uuid3(id2)}/versions/${uuid3(versionId)}/presentation`, {
|
|
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`, {
|
|
4899
5946
|
presentation: parse(presentationSchema, { schema_version: 1, header_mode: opts.header, color_scheme: opts.color })
|
|
4900
5947
|
}));
|
|
4901
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`)));
|