@webskill/sdk 0.1.4 → 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/dist/browser.d.ts +9 -4
- package/dist/browser.js +25 -22
- package/dist/{dist-MTc2KB03.js → dist-BJobG0i-.js} +30 -7
- package/dist/{dist-DAn07zHu.js → dist-BS5OpedX.js} +279 -63
- package/dist/{dist-DvoBsChX.js → dist-D0saNPi_.js} +154 -10
- package/dist/{dist-BVXZF7H_.js → dist-Dj6QjHBn.js} +148 -200
- package/dist/env--jJB-TSX-04klhTYi.js +143 -0
- package/dist/env-BPUBZCwJ-4jat_SVG.d.ts +38 -0
- package/dist/governance.d.ts +38 -6
- package/dist/governance.js +171 -11
- package/dist/{index-BSS3EWY-.d.ts → index-CySxIvRz.d.ts} +68 -3
- package/dist/{index-npFMt2Zw.d.ts → index-Vn63HJRO.d.ts} +15 -38
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +35 -9
- package/dist/node.d.ts +5 -4
- package/dist/node.js +5 -4
- package/dist/sandboxWorkerEntry.js +20 -3
- package/dist/{testing-S8SN9Lc6.js → testing-B4pq6JYa.js} +1 -1
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +3 -2
- package/dist/{types-CgNC-oQu-DYrxhD3z.d.ts → types-CKm5G_eQ-8W8FnP4u.d.ts} +41 -4
- package/dist/ui-react.d.ts +4 -2
- package/dist/ui-react.js +25 -4
- package/dist/ui-vue.d.ts +3 -2
- package/dist/ui-vue.js +44 -15
- package/dist/ui.d.ts +5 -3
- package/dist/ui.js +2 -2
- package/package.json +21 -9
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parse } from "yaml";
|
|
2
|
-
import { zipSync } from "fflate";
|
|
2
|
+
import { Unzip, UnzipInflate, zipSync } from "fflate";
|
|
3
3
|
|
|
4
4
|
//#region ../core/dist/index.js
|
|
5
5
|
/**
|
|
@@ -39,16 +39,22 @@ async function buildManifest(input) {
|
|
|
39
39
|
files: input.files
|
|
40
40
|
};
|
|
41
41
|
}
|
|
42
|
-
/** 按 manifest.files 比对实际 hash
|
|
43
|
-
function verifyManifest(manifest, actualHashes) {
|
|
42
|
+
/** 按 manifest.files 比对实际 hash 与文件集(mismatches/extras/missing 三类全空才 ok) */
|
|
43
|
+
function verifyManifest(manifest, actualHashes, actualFiles = [...actualHashes.keys()]) {
|
|
44
44
|
const mismatches = [];
|
|
45
|
+
const missing = [];
|
|
46
|
+
const listed = new Set(manifest.files.map((f) => f.path));
|
|
45
47
|
for (const file of manifest.files) {
|
|
46
48
|
const actual = actualHashes.get(file.path);
|
|
47
|
-
if (actual === void 0
|
|
49
|
+
if (actual === void 0) missing.push(file.path);
|
|
50
|
+
else if (actual !== file.sha256) mismatches.push(file.path);
|
|
48
51
|
}
|
|
52
|
+
const extras = actualFiles.filter((p) => p !== "webskill.skill-manifest.json" && p !== "skills.lock.json" && !listed.has(p)).sort();
|
|
49
53
|
return {
|
|
50
|
-
ok: mismatches.length === 0,
|
|
51
|
-
mismatches
|
|
54
|
+
ok: mismatches.length === 0 && missing.length === 0 && extras.length === 0,
|
|
55
|
+
mismatches,
|
|
56
|
+
extras,
|
|
57
|
+
missing
|
|
52
58
|
};
|
|
53
59
|
}
|
|
54
60
|
const ROOT = "/";
|
|
@@ -168,7 +174,26 @@ var MemoryFS = class {
|
|
|
168
174
|
for (const k of descendants) this.#entries.delete(k);
|
|
169
175
|
this.#entries.delete(key);
|
|
170
176
|
}
|
|
177
|
+
async rename(from, to) {
|
|
178
|
+
const src = this.#normalize(from);
|
|
179
|
+
const dst = this.#normalize(to);
|
|
180
|
+
const entry = this.#entries.get(src);
|
|
181
|
+
if (!entry) throw new WebSkillError("FS_NOT_FOUND", `Path not found: ${src}`);
|
|
182
|
+
const prefix = src + "/";
|
|
183
|
+
const descendants = [...this.#entries.entries()].filter(([k]) => k.startsWith(prefix));
|
|
184
|
+
this.#ensureParents(dst);
|
|
185
|
+
this.#entries.set(dst, entry);
|
|
186
|
+
for (const [k, v] of descendants) this.#entries.set(dst + k.slice(src.length), v);
|
|
187
|
+
for (const [k] of descendants) this.#entries.delete(k);
|
|
188
|
+
this.#entries.delete(src);
|
|
189
|
+
}
|
|
171
190
|
};
|
|
191
|
+
/** 原子写文本:先写临时文件再 rename(进程崩溃不留下半写文件;lockfile 等账本场景) */
|
|
192
|
+
async function atomicWriteText(fs, path, content) {
|
|
193
|
+
const tmp = `${path}.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
194
|
+
await fs.writeText(tmp, content);
|
|
195
|
+
await fs.rename(tmp, path);
|
|
196
|
+
}
|
|
172
197
|
/** 统一分隔符为 `/`,去除 `.` 段与重复分隔符(不解析 `..`) */
|
|
173
198
|
function normalizePath(path) {
|
|
174
199
|
return path.replace(/\\/g, "/").split("/").filter((s) => s !== "" && s !== ".").join("/");
|
|
@@ -407,6 +432,107 @@ function parseSkillPackManifest(text) {
|
|
|
407
432
|
}
|
|
408
433
|
return raw;
|
|
409
434
|
}
|
|
435
|
+
const DEFAULT_ARCHIVE_LIMITS = {
|
|
436
|
+
maxDownloadBytes: 64 * 1024 * 1024,
|
|
437
|
+
maxEntryBytes: 64 * 1024 * 1024,
|
|
438
|
+
maxTotalBytes: 256 * 1024 * 1024
|
|
439
|
+
};
|
|
440
|
+
function resolveArchiveLimits(limits) {
|
|
441
|
+
return {
|
|
442
|
+
...DEFAULT_ARCHIVE_LIMITS,
|
|
443
|
+
...limits
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
function concat(chunks, size) {
|
|
447
|
+
const out = new Uint8Array(size);
|
|
448
|
+
let offset = 0;
|
|
449
|
+
for (const chunk of chunks) {
|
|
450
|
+
out.set(chunk, offset);
|
|
451
|
+
offset += chunk.length;
|
|
452
|
+
}
|
|
453
|
+
return out;
|
|
454
|
+
}
|
|
455
|
+
/** 流式解 zip(单条目/总双重上限);返回 [path, content][](目录条目以 / 结尾、内容为空) */
|
|
456
|
+
async function unzipWithLimits(data, limits) {
|
|
457
|
+
const { maxEntryBytes, maxTotalBytes } = resolveArchiveLimits(limits);
|
|
458
|
+
let eocd = false;
|
|
459
|
+
const scanFrom = Math.max(0, data.length - 65557);
|
|
460
|
+
for (let i = data.length - 22; i >= scanFrom; i--) if (data[i] === 80 && data[i + 1] === 75 && data[i + 2] === 5 && data[i + 3] === 6) {
|
|
461
|
+
eocd = true;
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
if (!eocd) throw new WebSkillError("INSTALL_FAILED", "Failed to read zip archive: missing end-of-central-directory record");
|
|
465
|
+
const entries = [];
|
|
466
|
+
let total = 0;
|
|
467
|
+
let failure;
|
|
468
|
+
const unzip = new Unzip();
|
|
469
|
+
unzip.register(UnzipInflate);
|
|
470
|
+
unzip.onfile = (file) => {
|
|
471
|
+
if (file.name.endsWith("/")) {
|
|
472
|
+
entries.push([file.name, /* @__PURE__ */ new Uint8Array(0)]);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
const chunks = [];
|
|
476
|
+
let size = 0;
|
|
477
|
+
file.ondata = (err, chunk, final) => {
|
|
478
|
+
if (err) {
|
|
479
|
+
failure = err;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
size += chunk.length;
|
|
483
|
+
if (size > maxEntryBytes) {
|
|
484
|
+
failure = new WebSkillError("INSTALL_FAILED", `Archive entry exceeds the ${maxEntryBytes}-byte limit: ${file.name}`);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
total += chunk.length;
|
|
488
|
+
if (total > maxTotalBytes) {
|
|
489
|
+
failure = new WebSkillError("INSTALL_FAILED", `Archive contents exceed the ${maxTotalBytes}-byte total limit`);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
chunks.push(chunk);
|
|
493
|
+
if (final) entries.push([file.name, concat(chunks, size)]);
|
|
494
|
+
};
|
|
495
|
+
file.start();
|
|
496
|
+
};
|
|
497
|
+
try {
|
|
498
|
+
unzip.push(data, true);
|
|
499
|
+
} catch (e) {
|
|
500
|
+
throw new WebSkillError("INSTALL_FAILED", `Failed to read zip archive: ${e instanceof Error ? e.message : String(e)}`, e);
|
|
501
|
+
}
|
|
502
|
+
await new Promise((resolve) => {
|
|
503
|
+
setTimeout(resolve, 0);
|
|
504
|
+
});
|
|
505
|
+
if (failure) {
|
|
506
|
+
if (failure instanceof WebSkillError) throw failure;
|
|
507
|
+
throw new WebSkillError("INSTALL_FAILED", `Failed to read zip archive: ${failure instanceof Error ? failure.message : String(failure)}`, failure);
|
|
508
|
+
}
|
|
509
|
+
return entries;
|
|
510
|
+
}
|
|
511
|
+
/** 下载响应体:Content-Length 预检 + 流式累计上限 */
|
|
512
|
+
async function readResponseWithLimit(res, limits) {
|
|
513
|
+
const { maxDownloadBytes } = resolveArchiveLimits(limits);
|
|
514
|
+
const declared = res.headers.get("content-length");
|
|
515
|
+
if (declared !== null && Number(declared) > maxDownloadBytes) throw new WebSkillError("INSTALL_FAILED", `Download exceeds the ${maxDownloadBytes}-byte limit (Content-Length: ${declared})`);
|
|
516
|
+
if (!res.body) {
|
|
517
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
518
|
+
if (buf.length > maxDownloadBytes) throw new WebSkillError("INSTALL_FAILED", `Download exceeds the ${maxDownloadBytes}-byte limit`);
|
|
519
|
+
return buf;
|
|
520
|
+
}
|
|
521
|
+
const reader = res.body.getReader();
|
|
522
|
+
const chunks = [];
|
|
523
|
+
let size = 0;
|
|
524
|
+
for (;;) {
|
|
525
|
+
const { value, done } = await reader.read();
|
|
526
|
+
if (done) break;
|
|
527
|
+
size += value.length;
|
|
528
|
+
if (size > maxDownloadBytes) {
|
|
529
|
+
await reader.cancel().catch(() => void 0);
|
|
530
|
+
throw new WebSkillError("INSTALL_FAILED", `Download exceeds the ${maxDownloadBytes}-byte limit`);
|
|
531
|
+
}
|
|
532
|
+
chunks.push(value);
|
|
533
|
+
}
|
|
534
|
+
return concat(chunks, size);
|
|
535
|
+
}
|
|
410
536
|
/** 由条目列表构建 Catalog,保证按名称排序 */
|
|
411
537
|
function buildCatalog(entries) {
|
|
412
538
|
return { entries: [...entries].sort((a, b) => a.name.localeCompare(b.name)) };
|
|
@@ -463,8 +589,7 @@ var SkillDiscovery = class {
|
|
|
463
589
|
});
|
|
464
590
|
continue;
|
|
465
591
|
}
|
|
466
|
-
|
|
467
|
-
if (child.type !== "directory") continue;
|
|
592
|
+
await Promise.all((await this.#fs.list(root)).filter((child) => child.type === "directory").map(async (child) => {
|
|
468
593
|
const dirName = baseName(child.path);
|
|
469
594
|
const skillRoot = `${root}/${dirName}`;
|
|
470
595
|
const skillMdPath = `${skillRoot}/SKILL.md`;
|
|
@@ -489,7 +614,7 @@ var SkillDiscovery = class {
|
|
|
489
614
|
const dependencies = metadata["dependencies"];
|
|
490
615
|
adjacency.set(metadata.name, Array.isArray(dependencies) ? dependencies.filter((d) => typeof d === "string") : []);
|
|
491
616
|
}
|
|
492
|
-
}
|
|
617
|
+
}));
|
|
493
618
|
}
|
|
494
619
|
const cycles = checkDependencyCycles(adjacency);
|
|
495
620
|
const claimedNames = /* @__PURE__ */ new Set();
|
|
@@ -523,6 +648,25 @@ var SkillDiscovery = class {
|
|
|
523
648
|
});
|
|
524
649
|
}
|
|
525
650
|
issues.push(...cycles.issues);
|
|
651
|
+
const admitted = new Set(entries.map((e) => e.name));
|
|
652
|
+
let changed = true;
|
|
653
|
+
while (changed) {
|
|
654
|
+
changed = false;
|
|
655
|
+
for (const entry of [...entries]) {
|
|
656
|
+
const missing = (adjacency.get(entry.name) ?? []).filter((d) => !admitted.has(d));
|
|
657
|
+
if (missing.length === 0) continue;
|
|
658
|
+
entries.splice(entries.indexOf(entry), 1);
|
|
659
|
+
admitted.delete(entry.name);
|
|
660
|
+
this.#index.delete(entry.name);
|
|
661
|
+
issues.push({
|
|
662
|
+
code: "SKILL_UNKNOWN_DEPENDENCY",
|
|
663
|
+
severity: "error",
|
|
664
|
+
message: `Skill "${entry.name}" was excluded: dependency ${JSON.stringify(missing[0])} is not in the final catalog`,
|
|
665
|
+
path: entry.root
|
|
666
|
+
});
|
|
667
|
+
changed = true;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
526
670
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
527
671
|
return {
|
|
528
672
|
entries,
|
|
@@ -599,4 +743,4 @@ const xmlRenderer = {
|
|
|
599
743
|
};
|
|
600
744
|
|
|
601
745
|
//#endregion
|
|
602
|
-
export {
|
|
746
|
+
export { unzipWithLimits as A, parseSkillMarkdown as C, renderCatalogJson as D, renderAvailableSkillsXml as E, verifyManifest as M, xmlRenderer as N, resolveArchiveLimits as O, normalizePath as S, readResponseWithLimit as T, computeDigest as _, SKILL_NAME_MAX_LENGTH as a, isValidSkillName as b, SkillDiscovery as c, assertSafePathSegment as d, atomicWriteText as f, checkSkillRules as g, checkDependencyCycles as h, SKILL_MANIFEST_FILE as i, validateSkills as j, resolveInsideRoot as k, SkillReader as l, buildManifest as m, MemoryFS as n, SKILL_NAME_PATTERN as o, buildCatalog as p, SKILLS_LOCKFILE as r, SKILL_PACK_FILE as s, DEFAULT_ARCHIVE_LIMITS as t, WebSkillError as u, escapeXml as v, parseSkillPackManifest as w, jsonRenderer as x, exportSkills as y };
|