@clack-platform/cli 0.3.1 → 0.3.2
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/dist/cli.js +120 -15
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -28,6 +28,31 @@ var CliError = class extends Error {
|
|
|
28
28
|
return 1;
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
+
function describeDetails(details) {
|
|
32
|
+
if (!details || typeof details !== "object" || Array.isArray(details)) return [];
|
|
33
|
+
const d = details;
|
|
34
|
+
const text = (v) => typeof v === "string" && v ? v.replace(/[\p{C}]/gu, "").slice(0, 160) : typeof v === "number" || typeof v === "boolean" ? String(v) : void 0;
|
|
35
|
+
const lines = [];
|
|
36
|
+
const reason = text(d.reason);
|
|
37
|
+
if (reason) lines.push(`\uC0AC\uC720: ${reason}`);
|
|
38
|
+
const file = text(d.file);
|
|
39
|
+
if (file) lines.push(`\uD30C\uC77C: ${file}`);
|
|
40
|
+
if (Array.isArray(d.issues)) {
|
|
41
|
+
for (const issue of d.issues.slice(0, 5)) {
|
|
42
|
+
if (!issue || typeof issue !== "object") continue;
|
|
43
|
+
const i = issue;
|
|
44
|
+
const parts = [["\uD30C\uC77C", i.file], ["\uC704\uCE58", i.pointer], ["\uADDC\uCE59", i.rule], ["\uAC12", i.param]].flatMap(([label, v]) => {
|
|
45
|
+
const t = text(v);
|
|
46
|
+
return t ? [`${label}=${t}`] : [];
|
|
47
|
+
});
|
|
48
|
+
if (parts.length) lines.push(`\uBB38\uC81C: ${parts.join(" ")}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const expected = text(d.expected);
|
|
52
|
+
const actual = text(d.actual);
|
|
53
|
+
if (expected !== void 0 || actual !== void 0) lines.push(`\uAE30\uB300: ${expected ?? "-"} / \uC2E4\uC81C: ${actual ?? "-"}`);
|
|
54
|
+
return lines;
|
|
55
|
+
}
|
|
31
56
|
function apiError(status, body, retryAfter) {
|
|
32
57
|
const value2 = body && typeof body === "object" ? body : {};
|
|
33
58
|
const code = typeof value2.code === "string" ? value2.code : `HTTP_${status}`;
|
|
@@ -47,6 +72,9 @@ function apiError(status, body, retryAfter) {
|
|
|
47
72
|
if (code === "CONTENT_METADATA_EXTRAS_DISABLED") message += " \uC774 \uD658\uACBD\uC5D0\uC11C\uB294 \uC5B8\uC5B4\uBCC4 \uBA54\uD0C0\uB370\uC774\uD130\xB7\uD0DC\uADF8\uB97C \uC544\uC9C1 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.";
|
|
48
73
|
if (status === 423 || code === "USER_BANNED") message += " \uACC4\uC815 \uC81C\uD55C \uC0C1\uD0DC\uB97C \uC571\uC5D0\uC11C \uD655\uC778\uD558\uC138\uC694.";
|
|
49
74
|
if (status === 503 && code.endsWith("DISABLED")) message += " \uD604\uC7AC \uC678\uBD80 \uB3C4\uAD6C \uC811\uADFC\uC774 \uC77C\uC2DC \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
|
|
75
|
+
const located = describeDetails(value2.details);
|
|
76
|
+
if (located.length) message += `
|
|
77
|
+
${located.map((line) => ` ${line}`).join("\n")}`;
|
|
50
78
|
const raw = Number(value2.retry_after ?? retryAfter);
|
|
51
79
|
const retry = Number.isFinite(raw) && raw > 0 ? Math.ceil(raw) : void 0;
|
|
52
80
|
return new CliError(message, code, status, retry);
|
|
@@ -156,7 +184,7 @@ function redact(value2) {
|
|
|
156
184
|
// package.json
|
|
157
185
|
var package_default = {
|
|
158
186
|
name: "@clack-platform/cli",
|
|
159
|
-
version: "0.3.
|
|
187
|
+
version: "0.3.2",
|
|
160
188
|
description: "\uD074\uB799 \uACC4\uC815\uC758 \uC0C1\uD488\xB7\uCF58\uD150\uCE20\xB7\uD504\uB85C\uD544\uC744 \uAD00\uB9AC\uD558\uB294 CLI",
|
|
161
189
|
type: "module",
|
|
162
190
|
bin: {
|
|
@@ -2477,6 +2505,16 @@ function inspectIntroSemantics(skill, form, slots, fail2) {
|
|
|
2477
2505
|
if (!field2 || field2["x-clack-visibility"] !== "public") fail2(`/output/intro/${key}`, "\uACF5\uAC1C \uBB38\uC790\uC5F4(text\xB7textarea) \uD3FC \uD544\uB4DC\uB97C \uAC00\uB9AC\uCF1C\uC57C \uD569\uB2C8\uB2E4.");
|
|
2478
2506
|
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.`);
|
|
2479
2507
|
}
|
|
2508
|
+
let sectionLength = 0;
|
|
2509
|
+
for (const [key, name2] of Object.entries(intro.sections ?? {})) {
|
|
2510
|
+
const field2 = textField(name2);
|
|
2511
|
+
if (!field2 || field2["x-clack-visibility"] !== "public") fail2(`/output/intro/sections/${key}`, "\uC18C\uAC1C \uC139\uC158\uC740 \uACF5\uAC1C \uBB38\uC790\uC5F4(text\xB7textarea) \uD3FC \uD544\uB4DC\uB9CC \uAC00\uB9AC\uD0AC \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
2512
|
+
else {
|
|
2513
|
+
if (field2.maxLength > 2e3) fail2(`/output/intro/sections/${key}`, "\uC18C\uAC1C \uC139\uC158\uC758 \uCD5C\uB300 \uAE38\uC774\uB294 2,000\uC790\uC785\uB2C8\uB2E4.");
|
|
2514
|
+
sectionLength += field2.maxLength;
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
if (sectionLength > 6e3) fail2("/output/intro/sections", "\uC18C\uAC1C \uC139\uC158\uC758 \uCD5C\uB300 \uAE38\uC774 \uD569\uACC4\uB294 6,000\uC790\uC785\uB2C8\uB2E4.");
|
|
2480
2518
|
const fallback = intro.description_fallback ?? [];
|
|
2481
2519
|
let composed = 0;
|
|
2482
2520
|
for (const name2 of fallback) {
|
|
@@ -3831,6 +3869,37 @@ var clack_skill_v1_schema_default = {
|
|
|
3831
3869
|
minItems: 1,
|
|
3832
3870
|
maxItems: 8,
|
|
3833
3871
|
uniqueItems: true
|
|
3872
|
+
},
|
|
3873
|
+
sections: {
|
|
3874
|
+
type: "object",
|
|
3875
|
+
additionalProperties: false,
|
|
3876
|
+
description: "\uACF5\uAC1C \uBB38\uC790\uC5F4 \uD3FC \uD544\uB4DC\uB97C \uC18C\uAC1C \uC139\uC158\uC5D0 \uB9E4\uD551\uD569\uB2C8\uB2E4. \uD56D\uBAA9\uBCC4 \uCD5C\uB300 2,000\uC790, \uC120\uC5B8\uB41C \uD544\uB4DC \uCD5C\uB300 \uAE38\uC774 \uD569\uACC4 6,000\uC790\uC785\uB2C8\uB2E4.",
|
|
3877
|
+
properties: {
|
|
3878
|
+
how_to: {
|
|
3879
|
+
type: "string",
|
|
3880
|
+
pattern: "^[a-z][a-z0-9_-]{0,31}$"
|
|
3881
|
+
},
|
|
3882
|
+
notice: {
|
|
3883
|
+
type: "string",
|
|
3884
|
+
pattern: "^[a-z][a-z0-9_-]{0,31}$"
|
|
3885
|
+
},
|
|
3886
|
+
character: {
|
|
3887
|
+
type: "string",
|
|
3888
|
+
pattern: "^[a-z][a-z0-9_-]{0,31}$"
|
|
3889
|
+
},
|
|
3890
|
+
setting: {
|
|
3891
|
+
type: "string",
|
|
3892
|
+
pattern: "^[a-z][a-z0-9_-]{0,31}$"
|
|
3893
|
+
},
|
|
3894
|
+
relationship: {
|
|
3895
|
+
type: "string",
|
|
3896
|
+
pattern: "^[a-z][a-z0-9_-]{0,31}$"
|
|
3897
|
+
},
|
|
3898
|
+
first_scene: {
|
|
3899
|
+
type: "string",
|
|
3900
|
+
pattern: "^[a-z][a-z0-9_-]{0,31}$"
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3834
3903
|
}
|
|
3835
3904
|
}
|
|
3836
3905
|
}
|
|
@@ -5135,6 +5204,7 @@ var clack_content_v1_schema_default = {
|
|
|
5135
5204
|
var MAX_ZIP = 10 * 1024 * 1024;
|
|
5136
5205
|
var MAX_EXPANDED = 30 * 1024 * 1024;
|
|
5137
5206
|
var MAX_FILES2 = 500;
|
|
5207
|
+
var MAX_ZIP_ENTRIES = 2e3;
|
|
5138
5208
|
var textDecoder2 = new TextDecoder("utf-8", { fatal: true });
|
|
5139
5209
|
var ajv = new Ajv20202({ allErrors: true, strict: false });
|
|
5140
5210
|
ajv.addSchema(clack_content_v1_schema_default);
|
|
@@ -5179,6 +5249,14 @@ function skillPathAllowed(path) {
|
|
|
5179
5249
|
if (parts.includes("scripts") || /\.(?:sh|bash|zsh|fish|bat|cmd|ps1|exe|dll|so|dylib|wasm|py|rb|php|jar)$/i.test(path)) return false;
|
|
5180
5250
|
return true;
|
|
5181
5251
|
}
|
|
5252
|
+
function isArchiveMetadata(path) {
|
|
5253
|
+
const parts = path.split("/");
|
|
5254
|
+
const name2 = parts.at(-1) ?? "";
|
|
5255
|
+
return parts[0] === "__MACOSX" || name2 === ".DS_Store" || name2.startsWith("._");
|
|
5256
|
+
}
|
|
5257
|
+
function unsafeArchivePath(path) {
|
|
5258
|
+
return path.startsWith("/") || /[\p{Cc}]/u.test(path) || path.split("/").includes("..");
|
|
5259
|
+
}
|
|
5182
5260
|
async function collectFiles(directory) {
|
|
5183
5261
|
const files = {};
|
|
5184
5262
|
let totalBytes = 0;
|
|
@@ -5186,6 +5264,7 @@ async function collectFiles(directory) {
|
|
|
5186
5264
|
const entries = await readdir(join2(directory, relative), { withFileTypes: true });
|
|
5187
5265
|
for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
|
|
5188
5266
|
const path = relative ? `${relative}/${entry.name}` : entry.name;
|
|
5267
|
+
if (isArchiveMetadata(path)) continue;
|
|
5189
5268
|
if (!skillPathAllowed(path)) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD328\uD0A4\uC9C0 \uACBD\uB85C: ${path}`);
|
|
5190
5269
|
if (entry.isSymbolicLink() || !(entry.isDirectory() || entry.isFile())) invalid2(`\uC77C\uBC18 \uD30C\uC77C\xB7\uD3F4\uB354\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4: ${path}`);
|
|
5191
5270
|
if (entry.isDirectory()) {
|
|
@@ -5204,22 +5283,47 @@ async function collectFiles(directory) {
|
|
|
5204
5283
|
}
|
|
5205
5284
|
function inspectZip(bytes2, expectedDirectory, hashMismatch) {
|
|
5206
5285
|
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.");
|
|
5286
|
+
const raw = [];
|
|
5287
|
+
try {
|
|
5288
|
+
unzipSync2(bytes2, { filter(entry) {
|
|
5289
|
+
if (raw.length >= MAX_ZIP_ENTRIES) invalid2("ZIP \uD56D\uBAA9 \uC218\uAC00 2,000\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
5290
|
+
const dir = entry.name.endsWith("/");
|
|
5291
|
+
raw.push({ name: dir ? entry.name.slice(0, -1) : entry.name, dir });
|
|
5292
|
+
return false;
|
|
5293
|
+
} });
|
|
5294
|
+
} catch (error2) {
|
|
5295
|
+
if (error2 instanceof CliError) throw error2;
|
|
5296
|
+
return invalid2("ZIP \uAD6C\uC870\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
5297
|
+
}
|
|
5298
|
+
for (const { name: name2 } of raw) if (unsafeArchivePath(name2)) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${name2.replace(new RegExp("\\p{C}", "gu"), "").slice(0, 160)}`);
|
|
5299
|
+
const kept = raw.filter(({ name: name2 }) => !isArchiveMetadata(name2));
|
|
5300
|
+
const keptFiles = kept.filter(({ dir }) => !dir);
|
|
5301
|
+
if (keptFiles.length > MAX_FILES2) invalid2("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
5302
|
+
let prefix = "";
|
|
5303
|
+
const top = keptFiles[0]?.name.split("/")[0];
|
|
5304
|
+
if (top && keptFiles.every(({ name: name2 }) => name2.startsWith(`${top}/`))) {
|
|
5305
|
+
const names = new Set(keptFiles.map(({ name: name2 }) => name2));
|
|
5306
|
+
if (names.has(`${top}/SKILL.md`) && names.has(`${top}/clack.skill.json`)) prefix = `${top}/`;
|
|
5307
|
+
}
|
|
5207
5308
|
const seen = /* @__PURE__ */ new Set();
|
|
5208
|
-
|
|
5309
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
5310
|
+
for (const { name: name2, dir } of kept) {
|
|
5311
|
+
if (dir && prefix && name2 === prefix.slice(0, -1)) continue;
|
|
5312
|
+
const path = prefix && name2.startsWith(prefix) ? name2.slice(prefix.length) : name2;
|
|
5313
|
+
if (!skillPathAllowed(path) || seen.has(path.toLowerCase())) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${path}`);
|
|
5314
|
+
seen.add(path.toLowerCase());
|
|
5315
|
+
if (!dir) wanted.set(name2, path);
|
|
5316
|
+
}
|
|
5209
5317
|
let totalBytes = 0;
|
|
5210
|
-
|
|
5318
|
+
const extracted = {};
|
|
5211
5319
|
try {
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
if (entries > MAX_FILES2) invalid2("\uC2A4\uD0AC \uD30C\uC77C \uC218\uAC00 500\uAC1C\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
5215
|
-
const path = entry.name.endsWith("/") ? entry.name.slice(0, -1) : entry.name;
|
|
5216
|
-
if (!skillPathAllowed(path) || seen.has(path.toLowerCase())) invalid2(`\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 ZIP \uACBD\uB85C: ${path}`);
|
|
5217
|
-
seen.add(path.toLowerCase());
|
|
5218
|
-
if (entry.name.endsWith("/")) return false;
|
|
5320
|
+
const content = unzipSync2(bytes2, { filter(entry) {
|
|
5321
|
+
if (!wanted.has(entry.name)) return false;
|
|
5219
5322
|
totalBytes += entry.originalSize;
|
|
5220
5323
|
if (totalBytes > MAX_EXPANDED) invalid2("\uC2A4\uD0AC \uC555\uCD95 \uD574\uC81C \uC6A9\uB7C9\uC774 30 MiB\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.");
|
|
5221
5324
|
return true;
|
|
5222
5325
|
} });
|
|
5326
|
+
for (const [name2, data] of Object.entries(content)) extracted[wanted.get(name2)] = data;
|
|
5223
5327
|
} catch (error2) {
|
|
5224
5328
|
if (error2 instanceof CliError) throw error2;
|
|
5225
5329
|
return invalid2("ZIP \uAD6C\uC870\uB97C \uC77D\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
@@ -5462,11 +5566,12 @@ async function pushSkillVersion(client, prepared, skillId) {
|
|
|
5462
5566
|
if (error2 instanceof CliError && error2.code === "SKILL_VERSION_MAJOR_REQUIRED") {
|
|
5463
5567
|
throw new CliError(`${error2.message} (\uCDE8\uC18C: clack skill cancel ${resolvedSkillId} ${session2.version_id})`, error2.code, error2.status);
|
|
5464
5568
|
}
|
|
5465
|
-
if (error2 instanceof CliError)
|
|
5466
|
-
|
|
5467
|
-
|
|
5468
|
-
error2.
|
|
5469
|
-
|
|
5569
|
+
if (error2 instanceof CliError) {
|
|
5570
|
+
const judged = error2.status >= 400 && error2.status < 500 && error2.status !== 429;
|
|
5571
|
+
const hint = judged ? "\uD328\uD0A4\uC9C0\uB97C \uACE0\uCE5C \uB4A4 \uAC19\uC740 \uBC84\uC804\uC73C\uB85C \uB2E4\uC2DC clack skill push\uD558\uC138\uC694." : `\uC644\uB8CC \uC7AC\uC2DC\uB3C4: clack skill complete ${resolvedSkillId} ${session2.version_id}`;
|
|
5572
|
+
throw new CliError(`${error2.message}
|
|
5573
|
+
${hint}`, error2.code, error2.status, error2.retryAfter);
|
|
5574
|
+
}
|
|
5470
5575
|
throw error2;
|
|
5471
5576
|
}
|
|
5472
5577
|
}
|