buckyos 0.7.165 → 0.7.168
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/cli/dist/cli.mjs +590 -45
- package/cli/dist/cli.mjs.map +1 -1
- package/cli/modules/app.ts +9 -2
- package/cli/modules/pikg.ts +251 -5
- package/cli/modules/pikg_archive.ts +147 -34
- package/cli/modules/pikg_protocol.ts +207 -3
- package/cli/runtime/host.ts +2 -0
- package/cli/version.ts +1 -1
- package/dist/aicc_client.d.ts +1 -1
- package/dist/aicc_client.d.ts.map +1 -1
- package/dist/browser.cjs +1 -1
- package/dist/browser.mjs +2 -2
- package/dist/{executor_client-f9908517.mjs → executor_client-a32bc377.mjs} +4 -1
- package/dist/executor_client-a32bc377.mjs.map +1 -0
- package/dist/{executor_client-c8ff5698.js → executor_client-e026942b.js} +2 -2
- package/dist/executor_client-e026942b.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +2 -2
- package/dist/node.cjs +1 -1
- package/dist/node.mjs +2 -2
- package/dist/workflow_client.d.ts +11 -2
- package/dist/workflow_client.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/executor_client-c8ff5698.js.map +0 -1
- package/dist/executor_client-f9908517.mjs.map +0 -1
package/cli/dist/cli.mjs
CHANGED
|
@@ -614,6 +614,8 @@ function collectArgumentPaths(argv) {
|
|
|
614
614
|
} else if (["input", "session-token-file", "pikg"].includes(name)) {
|
|
615
615
|
if (value !== "-")
|
|
616
616
|
read.push(value);
|
|
617
|
+
} else if (name === "catalog" && module === "pikg") {
|
|
618
|
+
read.push(value);
|
|
617
619
|
} else if (name === "source" && module === "pikg") {
|
|
618
620
|
read.push(value);
|
|
619
621
|
} else if (name === "plan") {
|
|
@@ -3331,47 +3333,145 @@ async function gzipFile(source, destination) {
|
|
|
3331
3333
|
}
|
|
3332
3334
|
async function readTarGz(bytes, limit) {
|
|
3333
3335
|
const tar = await gunzipBytes(bytes, limit);
|
|
3334
|
-
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
3335
|
-
const text2 = (offset2, length) => decoder.decode(tar.subarray(offset2, offset2 + length)).replace(/\0.*$/s, "");
|
|
3336
3336
|
const files = [];
|
|
3337
3337
|
const seen = /* @__PURE__ */ new Set();
|
|
3338
3338
|
let offset = 0;
|
|
3339
3339
|
while (offset + BLOCK_SIZE <= tar.length && tar.subarray(offset, offset + BLOCK_SIZE).some(Boolean)) {
|
|
3340
|
-
|
|
3341
|
-
let size;
|
|
3342
|
-
let mode;
|
|
3343
|
-
try {
|
|
3344
|
-
const prefix = text2(offset + 257, 6) === "ustar" ? text2(offset + 345, 155) : "";
|
|
3345
|
-
raw = prefix ? `${prefix}/${text2(offset, 100)}` : text2(offset, 100);
|
|
3346
|
-
size = Number.parseInt(text2(offset + 124, 12).trim() || "0", 8);
|
|
3347
|
-
mode = Number.parseInt(text2(offset + 100, 8).trim() || "0", 8);
|
|
3348
|
-
} catch {
|
|
3349
|
-
throw invalidZip("payload", "tar header is not valid UTF-8");
|
|
3350
|
-
}
|
|
3351
|
-
const type = String.fromCharCode(tar[offset + 156] || 48);
|
|
3352
|
-
const path = raw.replace(/^(?:\.\/)+/, "").replace(/\/$/, "");
|
|
3340
|
+
const header = parseTarHeader(tar.subarray(offset, offset + BLOCK_SIZE));
|
|
3353
3341
|
const start = offset + BLOCK_SIZE;
|
|
3354
|
-
if (
|
|
3355
|
-
throw invalidZip("payload", `tar entry is truncated: ${
|
|
3356
|
-
}
|
|
3357
|
-
offset = start + Math.ceil(size / BLOCK_SIZE) * BLOCK_SIZE;
|
|
3358
|
-
if (type === "5" && (path === "" || path === "."))
|
|
3359
|
-
continue;
|
|
3360
|
-
if (!path || path.startsWith("/") || /^[A-Za-z]:/.test(path) || path.includes("\\") || path.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
|
|
3361
|
-
throw invalidZip("payload", `unsafe tar entry path: ${JSON.stringify(raw)}`);
|
|
3342
|
+
if (start + header.size > tar.length) {
|
|
3343
|
+
throw invalidZip("payload", `tar entry is truncated: ${header.path}`);
|
|
3362
3344
|
}
|
|
3363
|
-
|
|
3345
|
+
offset = start + Math.ceil(header.size / BLOCK_SIZE) * BLOCK_SIZE;
|
|
3346
|
+
if (header.kind !== "file")
|
|
3364
3347
|
continue;
|
|
3365
|
-
if (
|
|
3366
|
-
throw invalidZip("payload", `tar entry
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
throw invalidZip("payload", `duplicate tar entry: ${path}`);
|
|
3370
|
-
seen.add(path);
|
|
3371
|
-
files.push({ path, executable: (mode & 73) !== 0, bytes: tar.slice(start, start + size) });
|
|
3348
|
+
if (seen.has(header.path))
|
|
3349
|
+
throw invalidZip("payload", `duplicate tar entry: ${header.path}`);
|
|
3350
|
+
seen.add(header.path);
|
|
3351
|
+
files.push({ path: header.path, executable: header.executable, bytes: tar.slice(start, start + header.size) });
|
|
3372
3352
|
}
|
|
3373
3353
|
return files;
|
|
3374
3354
|
}
|
|
3355
|
+
function parseTarHeader(block) {
|
|
3356
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
3357
|
+
const text2 = (offset, length) => decoder.decode(block.subarray(offset, offset + length)).replace(/\0.*$/s, "");
|
|
3358
|
+
let raw;
|
|
3359
|
+
let size;
|
|
3360
|
+
let mode;
|
|
3361
|
+
try {
|
|
3362
|
+
const prefix = text2(257, 6) === "ustar" ? text2(345, 155) : "";
|
|
3363
|
+
raw = prefix ? `${prefix}/${text2(0, 100)}` : text2(0, 100);
|
|
3364
|
+
size = Number.parseInt(text2(124, 12).trim() || "0", 8);
|
|
3365
|
+
mode = Number.parseInt(text2(100, 8).trim() || "0", 8);
|
|
3366
|
+
} catch {
|
|
3367
|
+
throw invalidZip("payload", "tar header is not valid UTF-8");
|
|
3368
|
+
}
|
|
3369
|
+
if (!Number.isSafeInteger(size) || size < 0) {
|
|
3370
|
+
throw invalidZip("payload", `tar entry is truncated: ${raw}`);
|
|
3371
|
+
}
|
|
3372
|
+
const type = String.fromCharCode(block[156] || 48);
|
|
3373
|
+
const path = raw.replace(/^(?:\.\/)+/, "").replace(/\/$/, "");
|
|
3374
|
+
if (type === "5" && (path === "" || path === ".")) {
|
|
3375
|
+
return { path, kind: "root", size, executable: false };
|
|
3376
|
+
}
|
|
3377
|
+
if (!path || path.startsWith("/") || /^[A-Za-z]:/.test(path) || path.includes("\\") || path.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
|
|
3378
|
+
throw invalidZip("payload", `unsafe tar entry path: ${JSON.stringify(raw)}`);
|
|
3379
|
+
}
|
|
3380
|
+
if (type !== "5" && type !== "0") {
|
|
3381
|
+
throw invalidZip("payload", `tar entry is not a regular file or directory: ${path}`);
|
|
3382
|
+
}
|
|
3383
|
+
return { path, kind: type === "5" ? "directory" : "file", size, executable: (mode & 73) !== 0 };
|
|
3384
|
+
}
|
|
3385
|
+
async function listTarGzStream(gzip, limit, capture2) {
|
|
3386
|
+
const listing = { files: /* @__PURE__ */ new Map(), directories: /* @__PURE__ */ new Set(), captured: /* @__PURE__ */ new Map() };
|
|
3387
|
+
const header = new Uint8Array(BLOCK_SIZE);
|
|
3388
|
+
let headerFill = 0;
|
|
3389
|
+
let skip = 0;
|
|
3390
|
+
let body;
|
|
3391
|
+
let ended = false;
|
|
3392
|
+
let total = 0;
|
|
3393
|
+
try {
|
|
3394
|
+
const stream = gzip.pipeThrough(
|
|
3395
|
+
new DecompressionStream("gzip")
|
|
3396
|
+
);
|
|
3397
|
+
for await (const chunk of stream) {
|
|
3398
|
+
total += chunk.length;
|
|
3399
|
+
if (total > limit)
|
|
3400
|
+
throw invalidZip("limits", "payload expands beyond the size limit");
|
|
3401
|
+
let offset = 0;
|
|
3402
|
+
while (offset < chunk.length && !ended) {
|
|
3403
|
+
if (skip > 0) {
|
|
3404
|
+
const take2 = Math.min(skip, chunk.length - offset);
|
|
3405
|
+
if (body && body.fill < body.bytes.length) {
|
|
3406
|
+
const part = Math.min(take2, body.bytes.length - body.fill);
|
|
3407
|
+
body.bytes.set(chunk.subarray(offset, offset + part), body.fill);
|
|
3408
|
+
body.fill += part;
|
|
3409
|
+
if (body.fill === body.bytes.length) {
|
|
3410
|
+
listing.captured.set(body.path, body.bytes);
|
|
3411
|
+
body = void 0;
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
skip -= take2;
|
|
3415
|
+
offset += take2;
|
|
3416
|
+
continue;
|
|
3417
|
+
}
|
|
3418
|
+
const take = Math.min(BLOCK_SIZE - headerFill, chunk.length - offset);
|
|
3419
|
+
header.set(chunk.subarray(offset, offset + take), headerFill);
|
|
3420
|
+
headerFill += take;
|
|
3421
|
+
offset += take;
|
|
3422
|
+
if (headerFill < BLOCK_SIZE)
|
|
3423
|
+
break;
|
|
3424
|
+
headerFill = 0;
|
|
3425
|
+
if (!header.some(Boolean)) {
|
|
3426
|
+
ended = true;
|
|
3427
|
+
break;
|
|
3428
|
+
}
|
|
3429
|
+
const entry = parseTarHeader(header);
|
|
3430
|
+
skip = Math.ceil(entry.size / BLOCK_SIZE) * BLOCK_SIZE;
|
|
3431
|
+
if (entry.kind === "directory")
|
|
3432
|
+
listing.directories.add(entry.path);
|
|
3433
|
+
if (entry.kind !== "file")
|
|
3434
|
+
continue;
|
|
3435
|
+
if (listing.files.has(entry.path)) {
|
|
3436
|
+
throw invalidZip("payload", `duplicate tar entry: ${entry.path}`);
|
|
3437
|
+
}
|
|
3438
|
+
listing.files.set(entry.path, { size: entry.size, executable: entry.executable });
|
|
3439
|
+
if (capture2(entry.path, entry.size)) {
|
|
3440
|
+
if (entry.size === 0)
|
|
3441
|
+
listing.captured.set(entry.path, new Uint8Array());
|
|
3442
|
+
else
|
|
3443
|
+
body = { path: entry.path, bytes: new Uint8Array(entry.size), fill: 0 };
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
} catch (error) {
|
|
3448
|
+
if (error instanceof ToolError)
|
|
3449
|
+
throw error;
|
|
3450
|
+
throw invalidZip("compression", "payload is not a valid gzip stream");
|
|
3451
|
+
}
|
|
3452
|
+
if (skip > 0 || headerFill > 0 || body)
|
|
3453
|
+
throw invalidZip("payload", "tar stream is truncated");
|
|
3454
|
+
return listing;
|
|
3455
|
+
}
|
|
3456
|
+
function fileReadableStream(path) {
|
|
3457
|
+
let file;
|
|
3458
|
+
return new ReadableStream({
|
|
3459
|
+
async pull(controller) {
|
|
3460
|
+
file ??= await getHost().open(path, { read: true });
|
|
3461
|
+
const buffer = new Uint8Array(256 * 1024);
|
|
3462
|
+
const read = await file.read(buffer);
|
|
3463
|
+
if (read === null) {
|
|
3464
|
+
await file.close();
|
|
3465
|
+
controller.close();
|
|
3466
|
+
return;
|
|
3467
|
+
}
|
|
3468
|
+
controller.enqueue(buffer.subarray(0, read));
|
|
3469
|
+
},
|
|
3470
|
+
async cancel() {
|
|
3471
|
+
await file?.close();
|
|
3472
|
+
}
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3375
3475
|
async function gunzipBytes(bytes, limit) {
|
|
3376
3476
|
const stream = new ReadableStream({
|
|
3377
3477
|
start(controller) {
|
|
@@ -4163,11 +4263,10 @@ function derivedSelector(key) {
|
|
|
4163
4263
|
web: {},
|
|
4164
4264
|
agent: {},
|
|
4165
4265
|
agent_skills: {},
|
|
4166
|
-
agent_tools: {},
|
|
4167
4266
|
aiws: {},
|
|
4168
4267
|
script: {}
|
|
4169
4268
|
};
|
|
4170
|
-
return selectors[key];
|
|
4269
|
+
return selectors[key] ?? extToolsPlatformOfKey(key)?.selector;
|
|
4171
4270
|
}
|
|
4172
4271
|
function canonicalSelector(value, label) {
|
|
4173
4272
|
if (value === void 0)
|
|
@@ -4505,6 +4604,168 @@ async function verifyAiwsPayload(tarGz) {
|
|
|
4505
4604
|
templates: checked.manifest.workspace_templates.map((t) => ({ id: t.id, title: t.title, digest: t.digest, requires: t.requires }))
|
|
4506
4605
|
};
|
|
4507
4606
|
}
|
|
4607
|
+
const EXT_TOOLS_APP_TYPE = "ext_tools";
|
|
4608
|
+
const EXT_TOOLS_MANIFEST_FILE = "ext-manifest.json";
|
|
4609
|
+
const EXT_TOOLS_MANIFEST_SCHEMA = "buckyos.ext_tools/1";
|
|
4610
|
+
const MAX_EXT_TOOLS_PAYLOAD_BYTES = 64 * 1024 * 1024 * 1024;
|
|
4611
|
+
const MAX_EXT_TOOLS_LAUNCHER_BYTES = 1024 * 1024;
|
|
4612
|
+
const EXT_TOOLS_ID = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
4613
|
+
const EXT_TOOLS_COMMAND = /^[A-Za-z0-9_+-][A-Za-z0-9._+-]*$/;
|
|
4614
|
+
const EXT_TOOLS_WINDOWS_SUFFIXES = ["", ".exe", ".cmd", ".bat", ".ps1"];
|
|
4615
|
+
const BUILD_MACHINE_PATH = /(?:^|[\s"'=:(])(\/home\/|\/Users\/|\/root\/|\/tmp\/|\/var\/folders\/|\/opt\/buckyos\/|[A-Za-z]:\\\\?Users\\)/;
|
|
4616
|
+
const EXT_TOOLS_PLATFORMS = [
|
|
4617
|
+
{ name: "linux-x86_64", key: "ext_tools_linux_x86_64", selector: { os: "linux", arch: "x86_64" } },
|
|
4618
|
+
{ name: "linux-aarch64", key: "ext_tools_linux_aarch64", selector: { os: "linux", arch: "aarch64" } },
|
|
4619
|
+
{ name: "darwin-arm64", key: "ext_tools_darwin_arm64", selector: { os: "macos", arch: "aarch64" } },
|
|
4620
|
+
{ name: "darwin-x86_64", key: "ext_tools_darwin_x86_64", selector: { os: "macos", arch: "x86_64" } },
|
|
4621
|
+
{ name: "windows-x86_64", key: "ext_tools_windows_x86_64", selector: { os: "windows", arch: "x86_64" } }
|
|
4622
|
+
];
|
|
4623
|
+
function extToolsPlatformOfKey(key) {
|
|
4624
|
+
return EXT_TOOLS_PLATFORMS.find((platform) => platform.key === key);
|
|
4625
|
+
}
|
|
4626
|
+
function extToolsManifestErrors(value) {
|
|
4627
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4628
|
+
return ["must be a JSON object"];
|
|
4629
|
+
const manifest = value;
|
|
4630
|
+
const errors = [];
|
|
4631
|
+
const allowed = ["schema", "id", "version", "requires", "conflicts", "provides", "platforms", "size_bytes", "license", "desc"];
|
|
4632
|
+
for (const key of Object.keys(manifest))
|
|
4633
|
+
if (!allowed.includes(key))
|
|
4634
|
+
errors.push(`${key} is not a field of the manifest`);
|
|
4635
|
+
if (manifest.schema !== EXT_TOOLS_MANIFEST_SCHEMA)
|
|
4636
|
+
errors.push(`schema must be ${EXT_TOOLS_MANIFEST_SCHEMA}`);
|
|
4637
|
+
if (typeof manifest.id !== "string" || !EXT_TOOLS_ID.test(manifest.id))
|
|
4638
|
+
errors.push("id must be lowercase letters, digits and -");
|
|
4639
|
+
if (typeof manifest.version !== "string" || !manifest.version.trim())
|
|
4640
|
+
errors.push("version is required");
|
|
4641
|
+
if (!Array.isArray(manifest.provides) || manifest.provides.length === 0)
|
|
4642
|
+
errors.push("provides must not be empty");
|
|
4643
|
+
const commands = /* @__PURE__ */ new Set();
|
|
4644
|
+
for (const raw of Array.isArray(manifest.provides) ? manifest.provides : []) {
|
|
4645
|
+
const entry = raw ?? {};
|
|
4646
|
+
const command = entry.command;
|
|
4647
|
+
if (typeof command !== "string" || !EXT_TOOLS_COMMAND.test(command)) {
|
|
4648
|
+
errors.push(`provides: ${JSON.stringify(command)} is not a command name`);
|
|
4649
|
+
continue;
|
|
4650
|
+
}
|
|
4651
|
+
if (commands.has(command))
|
|
4652
|
+
errors.push(`provides: command ${command} is listed twice`);
|
|
4653
|
+
commands.add(command);
|
|
4654
|
+
if (typeof entry.version_probe !== "string" || !entry.version_probe.trim())
|
|
4655
|
+
errors.push(`provides: ${command} has no version_probe`);
|
|
4656
|
+
const smoke = entry.smoke;
|
|
4657
|
+
if (smoke !== void 0 && (typeof smoke !== "string" || !isSafeRelativePath(smoke))) {
|
|
4658
|
+
errors.push(`provides: the smoke of ${command} must be a relative path inside the payload`);
|
|
4659
|
+
}
|
|
4660
|
+
}
|
|
4661
|
+
if (!Array.isArray(manifest.platforms) || manifest.platforms.length === 0)
|
|
4662
|
+
errors.push("platforms must not be empty");
|
|
4663
|
+
for (const platform of Array.isArray(manifest.platforms) ? manifest.platforms : []) {
|
|
4664
|
+
if (!EXT_TOOLS_PLATFORMS.some((known) => known.name === platform))
|
|
4665
|
+
errors.push(`unknown platform ${JSON.stringify(platform)}`);
|
|
4666
|
+
}
|
|
4667
|
+
for (const raw of Array.isArray(manifest.requires) ? manifest.requires : manifest.requires === void 0 ? [] : [null]) {
|
|
4668
|
+
const required = raw ?? {};
|
|
4669
|
+
if (typeof required.id !== "string" || !EXT_TOOLS_ID.test(required.id) || required.id === manifest.id || typeof required.version !== "string") {
|
|
4670
|
+
errors.push(`requires: ${JSON.stringify(raw)} is not another package's {id, version}`);
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
if (manifest.conflicts !== void 0 && !(Array.isArray(manifest.conflicts) && manifest.conflicts.every((id) => typeof id === "string" && id !== manifest.id))) {
|
|
4674
|
+
errors.push("conflicts must list other packages by id");
|
|
4675
|
+
}
|
|
4676
|
+
return errors;
|
|
4677
|
+
}
|
|
4678
|
+
function isSafeRelativePath(path) {
|
|
4679
|
+
return !path.includes("\\") && !path.startsWith("/") && path.split("/").every((part) => part && part !== "." && part !== "..");
|
|
4680
|
+
}
|
|
4681
|
+
function parseExtToolsManifest(bytes) {
|
|
4682
|
+
let value;
|
|
4683
|
+
try {
|
|
4684
|
+
value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
4685
|
+
} catch (error) {
|
|
4686
|
+
throw invalid("ext_tools", `${EXT_TOOLS_MANIFEST_FILE} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
4687
|
+
}
|
|
4688
|
+
const errors = extToolsManifestErrors(value);
|
|
4689
|
+
if (errors.length > 0)
|
|
4690
|
+
throw invalid("ext_tools", `${EXT_TOOLS_MANIFEST_FILE}: ${errors.join("; ")}`);
|
|
4691
|
+
const manifest = value;
|
|
4692
|
+
manifest.requires ??= [];
|
|
4693
|
+
manifest.conflicts ??= [];
|
|
4694
|
+
return manifest;
|
|
4695
|
+
}
|
|
4696
|
+
function extToolsPayloadErrors(manifest, platform, tree, buildPaths = []) {
|
|
4697
|
+
const errors = [];
|
|
4698
|
+
const windows = platform.startsWith("windows-");
|
|
4699
|
+
const suffixes = windows ? EXT_TOOLS_WINDOWS_SUFFIXES : [""];
|
|
4700
|
+
for (const path of [...tree.files.keys(), ...tree.directories]) {
|
|
4701
|
+
const [top, second] = path.split("/");
|
|
4702
|
+
const isTop = !path.includes("/");
|
|
4703
|
+
const known = top === EXT_TOOLS_MANIFEST_FILE && isTop || top === "bin" || top === "smoke" || top === "store";
|
|
4704
|
+
if (!known)
|
|
4705
|
+
errors.push(`${path}: a package only holds ${EXT_TOOLS_MANIFEST_FILE}, bin/, smoke/ and store/${manifest.id}/`);
|
|
4706
|
+
if (top === "store" && second !== void 0 && second !== manifest.id)
|
|
4707
|
+
errors.push(`${path}: a package only writes store/${manifest.id}/`);
|
|
4708
|
+
}
|
|
4709
|
+
const declared = new Set(manifest.provides.map((provided) => provided.command));
|
|
4710
|
+
for (const provided of manifest.provides) {
|
|
4711
|
+
const launcher = suffixes.map((suffix) => tree.files.get(`bin/${provided.command}${suffix}`)).find(Boolean);
|
|
4712
|
+
if (!launcher)
|
|
4713
|
+
errors.push(`bin/${provided.command}: the launcher of a provided command is missing`);
|
|
4714
|
+
else if (!windows && !launcher.executable)
|
|
4715
|
+
errors.push(`bin/${provided.command}: the launcher is not executable`);
|
|
4716
|
+
if (provided.smoke && !tree.files.has(provided.smoke))
|
|
4717
|
+
errors.push(`${provided.smoke}: the smoke script of ${provided.command} is missing`);
|
|
4718
|
+
}
|
|
4719
|
+
for (const path of tree.files.keys()) {
|
|
4720
|
+
if (!path.startsWith("bin/"))
|
|
4721
|
+
continue;
|
|
4722
|
+
const name = path.slice("bin/".length);
|
|
4723
|
+
const command = suffixes.map((suffix) => suffix && name.endsWith(suffix) ? name.slice(0, -suffix.length) : name).find((candidate) => declared.has(candidate));
|
|
4724
|
+
if (!command)
|
|
4725
|
+
errors.push(`${path}: not in provides, so nothing knows this package provides it`);
|
|
4726
|
+
}
|
|
4727
|
+
const decoder = new TextDecoder();
|
|
4728
|
+
for (const [path, bytes] of tree.launchers) {
|
|
4729
|
+
const text2 = decoder.decode(bytes);
|
|
4730
|
+
const named = buildPaths.find((dir) => text2.includes(dir));
|
|
4731
|
+
if (named)
|
|
4732
|
+
errors.push(`${path}: names ${named} on the machine that built it`);
|
|
4733
|
+
else if (BUILD_MACHINE_PATH.test(text2))
|
|
4734
|
+
errors.push(`${path}: names an absolute path of the machine that built it (${BUILD_MACHINE_PATH.exec(text2)?.[1]})`);
|
|
4735
|
+
}
|
|
4736
|
+
return errors;
|
|
4737
|
+
}
|
|
4738
|
+
function assertExtToolsOf(appDoc, manifest, platform, platforms) {
|
|
4739
|
+
if (manifest.id !== appDoc.name)
|
|
4740
|
+
throw invalid("ext_tools", `${EXT_TOOLS_MANIFEST_FILE} id ${manifest.id} is not the App name ${appDoc.name}`);
|
|
4741
|
+
if (manifest.version !== appDoc.version)
|
|
4742
|
+
throw invalid("ext_tools", `${EXT_TOOLS_MANIFEST_FILE} version ${manifest.version} is not the App version ${appDoc.version}`);
|
|
4743
|
+
if (!manifest.platforms.includes(platform))
|
|
4744
|
+
throw invalid("ext_tools", `${EXT_TOOLS_MANIFEST_FILE} does not list the payload's platform ${platform}`);
|
|
4745
|
+
const missing = manifest.platforms.filter((name) => !platforms.includes(name));
|
|
4746
|
+
if (missing.length > 0)
|
|
4747
|
+
throw invalid("ext_tools", `${EXT_TOOLS_MANIFEST_FILE} lists platforms without a payload: ${missing.join(", ")}`);
|
|
4748
|
+
}
|
|
4749
|
+
async function verifyExtToolsPayload(gzip, key, appDoc) {
|
|
4750
|
+
const platform = extToolsPlatformOfKey(key);
|
|
4751
|
+
if (!platform)
|
|
4752
|
+
throw invalid("appdoc", `an ext_tools package has no sub package ${key}`);
|
|
4753
|
+
const listing = await listTarGzStream(
|
|
4754
|
+
gzip,
|
|
4755
|
+
MAX_EXT_TOOLS_PAYLOAD_BYTES,
|
|
4756
|
+
(path, size) => path === EXT_TOOLS_MANIFEST_FILE || path.startsWith("bin/") && size <= MAX_EXT_TOOLS_LAUNCHER_BYTES
|
|
4757
|
+
);
|
|
4758
|
+
const raw = listing.captured.get(EXT_TOOLS_MANIFEST_FILE);
|
|
4759
|
+
if (!raw)
|
|
4760
|
+
throw invalid("ext_tools", `${key}: ${EXT_TOOLS_MANIFEST_FILE} is not at the root of the payload`);
|
|
4761
|
+
const manifest = parseExtToolsManifest(raw);
|
|
4762
|
+
assertExtToolsOf(appDoc, manifest, platform.name, Object.keys(expectObject$3(appDoc.pkg_list, "APPDOC.pkg_list")).flatMap((k) => extToolsPlatformOfKey(k)?.name ?? []));
|
|
4763
|
+
const launchers = new Map([...listing.captured].filter(([path]) => path.startsWith("bin/")));
|
|
4764
|
+
const errors = extToolsPayloadErrors(manifest, platform.name, { files: listing.files, directories: listing.directories, launchers });
|
|
4765
|
+
if (errors.length > 0)
|
|
4766
|
+
throw invalid("ext_tools", `${key}: ${errors.join("; ")}`);
|
|
4767
|
+
return manifest;
|
|
4768
|
+
}
|
|
4508
4769
|
async function validateSnapshot(distDir, appDoc, packageMeta) {
|
|
4509
4770
|
const payloads = /* @__PURE__ */ new Map();
|
|
4510
4771
|
for (const entryValue of Object.values(packageMeta.content_index)) {
|
|
@@ -4518,6 +4779,10 @@ async function validateSnapshot(distDir, appDoc, packageMeta) {
|
|
|
4518
4779
|
for (const sub of validated.subpackages)
|
|
4519
4780
|
await verifyAiwsPayload(await getHost().readFile(join$1(distDir, sub.payload_path)));
|
|
4520
4781
|
}
|
|
4782
|
+
if (appDoc.app_type === EXT_TOOLS_APP_TYPE) {
|
|
4783
|
+
for (const sub of validated.subpackages)
|
|
4784
|
+
await verifyExtToolsPayload(fileReadableStream(join$1(distDir, sub.payload_path)), sub.key, appDoc);
|
|
4785
|
+
}
|
|
4521
4786
|
return validated;
|
|
4522
4787
|
}
|
|
4523
4788
|
async function packSnapshot(distDir, destination, appDoc, packageMeta) {
|
|
@@ -4628,10 +4893,18 @@ async function inspectPikg(path) {
|
|
|
4628
4893
|
const sub = validated.subpackages[0];
|
|
4629
4894
|
aiws2 = await verifyAiwsPayload(await readZipEntry(archive, archive.byName.get(sub.payload_path), MAX_AIWS_PAYLOAD_BYTES));
|
|
4630
4895
|
}
|
|
4896
|
+
let extTools;
|
|
4897
|
+
if (appDoc.app_type === EXT_TOOLS_APP_TYPE) {
|
|
4898
|
+
for (const sub of validated.subpackages) {
|
|
4899
|
+
const manifest = await verifyExtToolsPayload(zipEntryStream(archive.path, archive.byName.get(sub.payload_path)), sub.key, appDoc);
|
|
4900
|
+
extTools ??= { ...manifest };
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4631
4903
|
const pikgDigest = await digestFile(path);
|
|
4632
4904
|
return {
|
|
4633
4905
|
schema_version: 1,
|
|
4634
4906
|
...aiws2 ? { aiws: aiws2 } : {},
|
|
4907
|
+
...extTools ? { ext_tools: extTools } : {},
|
|
4635
4908
|
protocol: PACKAGE_META_SCHEMA,
|
|
4636
4909
|
pikg_path: resolve$2(path),
|
|
4637
4910
|
size: pikgDigest.size,
|
|
@@ -4928,7 +5201,7 @@ function validateAppDocShape(appDoc) {
|
|
|
4928
5201
|
}
|
|
4929
5202
|
if (appDoc.doc_type !== "app")
|
|
4930
5203
|
throw invalid("appdoc", "APPDOC.doc_type must be app");
|
|
4931
|
-
if (!["service", "dapp", "web", "agent", "agent_skills", "aiws"].includes(String(appDoc.app_type))) {
|
|
5204
|
+
if (!["service", "dapp", "web", "agent", "agent_skills", "aiws", EXT_TOOLS_APP_TYPE].includes(String(appDoc.app_type))) {
|
|
4932
5205
|
throw invalid("appdoc", "APPDOC.app_type is invalid");
|
|
4933
5206
|
}
|
|
4934
5207
|
if (appDoc.name !== void 0)
|
|
@@ -4989,6 +5262,16 @@ function validateAppDocShape(appDoc) {
|
|
|
4989
5262
|
if (appDoc.app_type === "aiws" && Object.keys(pkgList).join() !== AIWS_SUB_PACKAGE) {
|
|
4990
5263
|
throw invalid("appdoc", "an aiws package must contain only the aiws sub package");
|
|
4991
5264
|
}
|
|
5265
|
+
if (appDoc.app_type === EXT_TOOLS_APP_TYPE) {
|
|
5266
|
+
expectNonEmptyString(appDoc.name, "APPDOC.name");
|
|
5267
|
+
const unknown = Object.keys(pkgList).find((key) => !extToolsPlatformOfKey(key));
|
|
5268
|
+
if (unknown)
|
|
5269
|
+
throw invalid("appdoc", `an ext_tools package must contain only ext_tools_<os>_<arch> sub packages, not ${unknown}`);
|
|
5270
|
+
} else {
|
|
5271
|
+
const stray = Object.keys(pkgList).find((key) => extToolsPlatformOfKey(key));
|
|
5272
|
+
if (stray)
|
|
5273
|
+
throw invalid("appdoc", `${stray} belongs to an ext_tools package`);
|
|
5274
|
+
}
|
|
4992
5275
|
deriveNamespaceForPackage(appDoc);
|
|
4993
5276
|
}
|
|
4994
5277
|
function deriveNamespaceForPackage(appDoc) {
|
|
@@ -5242,7 +5525,7 @@ function validateContentHandlers(value, permissions) {
|
|
|
5242
5525
|
}
|
|
5243
5526
|
return value;
|
|
5244
5527
|
}
|
|
5245
|
-
const PACKAGE_VERSION = "0.7.
|
|
5528
|
+
const PACKAGE_VERSION = "0.7.168";
|
|
5246
5529
|
const TOOL_VERSION = PACKAGE_VERSION;
|
|
5247
5530
|
const SDK_VERSION = PACKAGE_VERSION;
|
|
5248
5531
|
const PROTOCOL_VERSION = "1";
|
|
@@ -5260,14 +5543,17 @@ const SHA256_ID = /^sha256:[0-9a-f]{64}$/;
|
|
|
5260
5543
|
const OBJECT_ID = /^(?:appdoc|pkg):[0-9a-f]{64}$/;
|
|
5261
5544
|
const SAFE_GENERATED_FILE = /^[A-Za-z0-9._-]+$/;
|
|
5262
5545
|
const OBJECT_OUTPUT$c = { type: "object", additionalProperties: true };
|
|
5263
|
-
const APP_KINDS = ["static-web", "script", "docker", "agent-skills", "aiws-controls"];
|
|
5546
|
+
const APP_KINDS = ["static-web", "script", "docker", "agent-skills", "aiws-controls", "ext-tools"];
|
|
5264
5547
|
const PATH_KINDS = {
|
|
5265
5548
|
"static-web": { key: "web", category: "web", selectorType: "static" },
|
|
5266
5549
|
script: { key: "script", category: "dapp", selectorType: "single" },
|
|
5267
5550
|
"agent-skills": { key: "agent_skills", category: "agent_skills", selectorType: "single" },
|
|
5268
5551
|
// AI Workspace controls: one bundle (aiws-manifest.json + controls/<dir>/), installed into the AI Workspace library
|
|
5269
|
-
"aiws-controls": { key: "aiws", category: "aiws", selectorType: "single" }
|
|
5552
|
+
"aiws-controls": { key: "aiws", category: "aiws", selectorType: "single" },
|
|
5553
|
+
// Extension tools: ext-manifest.json + payload/<platform>/, one sub package per platform, enabled for the Zone
|
|
5554
|
+
"ext-tools": { key: "ext_tools", category: "ext_tools", selectorType: "single" }
|
|
5270
5555
|
};
|
|
5556
|
+
const EXT_TOOLS_LAUNCHER_LIMIT = 1024 * 1024;
|
|
5271
5557
|
function packageEnvironmentQualifier(key, selector) {
|
|
5272
5558
|
const effective = selector ?? derivedSelector(key);
|
|
5273
5559
|
const os = effective?.os;
|
|
@@ -5299,9 +5585,19 @@ function createPikgModule(dependencies = {}) {
|
|
|
5299
5585
|
positionals: [
|
|
5300
5586
|
{ name: "meta_dir", description: "dapp_meta directory", required: false }
|
|
5301
5587
|
],
|
|
5588
|
+
options: [
|
|
5589
|
+
{
|
|
5590
|
+
name: "catalog",
|
|
5591
|
+
description: "ext-tools: a directory of other packages (<id>/ext-manifest.json) to check requires and same-named commands against",
|
|
5592
|
+
type: "string"
|
|
5593
|
+
}
|
|
5594
|
+
],
|
|
5302
5595
|
inputSchema: {
|
|
5303
5596
|
type: "object",
|
|
5304
|
-
properties: {
|
|
5597
|
+
properties: {
|
|
5598
|
+
meta_dir: { type: "string", minLength: 1 },
|
|
5599
|
+
catalog: { type: "string", minLength: 1 }
|
|
5600
|
+
},
|
|
5305
5601
|
additionalProperties: false
|
|
5306
5602
|
},
|
|
5307
5603
|
outputSchema: OBJECT_OUTPUT$c,
|
|
@@ -5311,7 +5607,11 @@ function createPikgModule(dependencies = {}) {
|
|
|
5311
5607
|
requiresSession: false,
|
|
5312
5608
|
execution: "local",
|
|
5313
5609
|
networkAccess: false,
|
|
5314
|
-
examples: [
|
|
5610
|
+
examples: [
|
|
5611
|
+
"buckyos pikg doctor",
|
|
5612
|
+
"buckyos pikg doctor ./dapp_meta",
|
|
5613
|
+
"buckyos pikg doctor ./dapp_meta --catalog ../publish/exttool"
|
|
5614
|
+
],
|
|
5315
5615
|
handler: async (ctx, input) => await doctorCommand(ctx, input)
|
|
5316
5616
|
},
|
|
5317
5617
|
{
|
|
@@ -5369,7 +5669,8 @@ function createPikgModule(dependencies = {}) {
|
|
|
5369
5669
|
"buckyos --non-interactive pikg init . --owner did:bns:root --kind static-web --source ./web/dist",
|
|
5370
5670
|
"buckyos --non-interactive pikg init . --owner did:bns:root --kind agent-skills --source ./skills",
|
|
5371
5671
|
"buckyos --non-interactive pikg init . --owner did:bns:alice --kind aiws-controls --source ./aiws",
|
|
5372
|
-
"buckyos --non-interactive pikg init . --owner did:bns:alice --kind aiws-controls --source ./aiws --composite"
|
|
5672
|
+
"buckyos --non-interactive pikg init . --owner did:bns:alice --kind aiws-controls --source ./aiws --composite",
|
|
5673
|
+
"buckyos --non-interactive pikg init . --name ext-media --owner did:bns:root --kind ext-tools --source ./ext-media"
|
|
5373
5674
|
],
|
|
5374
5675
|
handler: async (ctx, input) => await initCommand(ctx, input, docker)
|
|
5375
5676
|
},
|
|
@@ -5527,6 +5828,10 @@ async function doctorCommand(ctx, input) {
|
|
|
5527
5828
|
status: missingSources.length === 0 ? "ok" : "missing",
|
|
5528
5829
|
missing: missingSources
|
|
5529
5830
|
});
|
|
5831
|
+
if (app.categories[0] === PATH_KINDS["ext-tools"].category) {
|
|
5832
|
+
const catalog = optionalInputString(input, "catalog");
|
|
5833
|
+
checks.push(...await extToolsChecks(app, pikg, metaDir, catalog ? resolveFromCwd$1(ctx, catalog) : void 0));
|
|
5834
|
+
}
|
|
5530
5835
|
} catch (error) {
|
|
5531
5836
|
checks.push({
|
|
5532
5837
|
name: "metadata_schema",
|
|
@@ -5540,7 +5845,7 @@ async function doctorCommand(ctx, input) {
|
|
|
5540
5845
|
const profile = profileName ? await ctx.configStore.readProfile(profileName) : void 0;
|
|
5541
5846
|
const host2 = getHost();
|
|
5542
5847
|
return {
|
|
5543
|
-
healthy: checks.every((check) => check.status === "ok"),
|
|
5848
|
+
healthy: checks.every((check) => check.status === "ok" || check.status === "skipped"),
|
|
5544
5849
|
tool_version: TOOL_VERSION,
|
|
5545
5850
|
sdk_version: SDK_VERSION,
|
|
5546
5851
|
protocol_version: PROTOCOL_VERSION,
|
|
@@ -5591,7 +5896,25 @@ async function initCommand(ctx, input, docker) {
|
|
|
5591
5896
|
let sourceValue;
|
|
5592
5897
|
let selector;
|
|
5593
5898
|
let sourceSummary2;
|
|
5594
|
-
if (kind === "
|
|
5899
|
+
if (kind === "ext-tools") {
|
|
5900
|
+
const platform = hostExtToolsPlatform();
|
|
5901
|
+
const absoluteSource = resolve$1(projectDir, source);
|
|
5902
|
+
subpackageKey = platform.key;
|
|
5903
|
+
selector = platform.selector;
|
|
5904
|
+
sourceValue = {
|
|
5905
|
+
type: "path",
|
|
5906
|
+
path: toPortablePath(relative(join(projectDir, "dapp_meta"), join(absoluteSource, "payload", platform.name)))
|
|
5907
|
+
};
|
|
5908
|
+
sourceSummary2 = { type: "path", path: displaySource(projectDir, absoluteSource), platform: platform.name };
|
|
5909
|
+
try {
|
|
5910
|
+
await getHost().lstat(join(absoluteSource, EXT_TOOLS_MANIFEST_FILE));
|
|
5911
|
+
} catch (error) {
|
|
5912
|
+
if (!isHostError(error, "NotFound"))
|
|
5913
|
+
throw error;
|
|
5914
|
+
await writeExtToolsSkeleton(absoluteSource, name, version, platform.name);
|
|
5915
|
+
sourceSummary2.skeleton = true;
|
|
5916
|
+
}
|
|
5917
|
+
} else if (kind === "docker") {
|
|
5595
5918
|
const image = await docker.inspect(source);
|
|
5596
5919
|
const arch = normalizeArch$1(image.architecture);
|
|
5597
5920
|
subpackageKey = arch === "x86_64" ? "amd64_docker_image" : arch === "aarch64" ? "aarch64_docker_image" : (() => {
|
|
@@ -5696,6 +6019,221 @@ async function writeAiwsSkeleton(dir, ownerDid, name, composite) {
|
|
|
5696
6019
|
await host2.writeTextFile(join(dir, AIWS_MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
5697
6020
|
`);
|
|
5698
6021
|
}
|
|
6022
|
+
function hostExtToolsPlatform() {
|
|
6023
|
+
const host2 = getHost();
|
|
6024
|
+
const os = host2.platform === "darwin" ? "macos" : host2.platform === "win32" ? "windows" : host2.platform;
|
|
6025
|
+
const arch = normalizeArch$1(host2.arch);
|
|
6026
|
+
const platform = EXT_TOOLS_PLATFORMS.find((known) => known.selector.os === os && known.selector.arch === arch);
|
|
6027
|
+
if (!platform) {
|
|
6028
|
+
throw new UsageError("UNSUPPORTED_PLATFORM", `extension packages have no payload for ${os}/${arch}`);
|
|
6029
|
+
}
|
|
6030
|
+
return platform;
|
|
6031
|
+
}
|
|
6032
|
+
async function writeExtToolsSkeleton(dir, id, version, platform) {
|
|
6033
|
+
const host2 = getHost();
|
|
6034
|
+
const manifest = {
|
|
6035
|
+
schema: EXT_TOOLS_MANIFEST_SCHEMA,
|
|
6036
|
+
id,
|
|
6037
|
+
version,
|
|
6038
|
+
requires: [],
|
|
6039
|
+
conflicts: [],
|
|
6040
|
+
provides: [{ command: id, version_probe: `${id} --version`, smoke: `smoke/${id}.sh` }],
|
|
6041
|
+
platforms: [platform],
|
|
6042
|
+
license: "",
|
|
6043
|
+
desc: ""
|
|
6044
|
+
};
|
|
6045
|
+
const text2 = `${JSON.stringify(manifest, null, 2)}
|
|
6046
|
+
`;
|
|
6047
|
+
const payload = join(dir, "payload", platform);
|
|
6048
|
+
for (const sub of ["bin", "smoke", join("store", id)])
|
|
6049
|
+
await host2.mkdir(join(payload, sub), { recursive: true });
|
|
6050
|
+
await host2.writeTextFile(join(dir, EXT_TOOLS_MANIFEST_FILE), text2);
|
|
6051
|
+
await host2.writeTextFile(join(payload, EXT_TOOLS_MANIFEST_FILE), text2);
|
|
6052
|
+
const windows = platform.startsWith("windows-");
|
|
6053
|
+
await host2.writeTextFile(
|
|
6054
|
+
join(payload, "bin", windows ? `${id}.cmd` : id),
|
|
6055
|
+
windows ? `@echo off\r
|
|
6056
|
+
echo ${id} ${version}\r
|
|
6057
|
+
` : `#!/bin/sh
|
|
6058
|
+
echo "${id} ${version}"
|
|
6059
|
+
`,
|
|
6060
|
+
{ mode: 493 }
|
|
6061
|
+
);
|
|
6062
|
+
await host2.writeTextFile(join(payload, "smoke", `${id}.sh`), `#!/bin/sh
|
|
6063
|
+
set -eu
|
|
6064
|
+
${id} --version >/dev/null
|
|
6065
|
+
`, { mode: 493 });
|
|
6066
|
+
}
|
|
6067
|
+
async function readExtToolsManifestFile(path) {
|
|
6068
|
+
let text2;
|
|
6069
|
+
try {
|
|
6070
|
+
text2 = await getHost().readTextFile(path);
|
|
6071
|
+
} catch (error) {
|
|
6072
|
+
if (isHostError(error, "NotFound"))
|
|
6073
|
+
return { text: "", errors: ["not found"] };
|
|
6074
|
+
throw error;
|
|
6075
|
+
}
|
|
6076
|
+
let value;
|
|
6077
|
+
try {
|
|
6078
|
+
value = JSON.parse(text2);
|
|
6079
|
+
} catch {
|
|
6080
|
+
return { text: text2, errors: ["not valid JSON"] };
|
|
6081
|
+
}
|
|
6082
|
+
const errors = extToolsManifestErrors(value);
|
|
6083
|
+
if (errors.length > 0)
|
|
6084
|
+
return { text: text2, errors };
|
|
6085
|
+
const manifest = value;
|
|
6086
|
+
manifest.requires ??= [];
|
|
6087
|
+
manifest.conflicts ??= [];
|
|
6088
|
+
return { text: text2, manifest, errors };
|
|
6089
|
+
}
|
|
6090
|
+
async function readExtToolsPayloadTree(root) {
|
|
6091
|
+
const host2 = getHost();
|
|
6092
|
+
const tree = { files: /* @__PURE__ */ new Map(), directories: /* @__PURE__ */ new Set(), launchers: /* @__PURE__ */ new Map() };
|
|
6093
|
+
const errors = [];
|
|
6094
|
+
const realRoot = await host2.realPath(root);
|
|
6095
|
+
const walk = async (directory, prefix) => {
|
|
6096
|
+
const children = await host2.readDir(directory);
|
|
6097
|
+
for (const child of children) {
|
|
6098
|
+
const archivePath = prefix ? `${prefix}/${child.name}` : child.name;
|
|
6099
|
+
const path = join(directory, child.name);
|
|
6100
|
+
const link = await host2.lstat(path);
|
|
6101
|
+
let physical = path;
|
|
6102
|
+
if (link.isSymlink) {
|
|
6103
|
+
try {
|
|
6104
|
+
physical = await host2.realPath(path);
|
|
6105
|
+
} catch (error) {
|
|
6106
|
+
errors.push(`${archivePath}: ${isHostError(error, "NotFound") ? "a dangling symlink" : "a symlink out of the payload"}`);
|
|
6107
|
+
continue;
|
|
6108
|
+
}
|
|
6109
|
+
const inside = relative(realRoot, physical);
|
|
6110
|
+
if (inside === ".." || inside.startsWith(`..${host2.path.sep}`) || isAbsolute$1(inside)) {
|
|
6111
|
+
errors.push(`${archivePath}: a symlink out of the payload`);
|
|
6112
|
+
continue;
|
|
6113
|
+
}
|
|
6114
|
+
}
|
|
6115
|
+
const info = link.isSymlink ? await host2.stat(physical) : link;
|
|
6116
|
+
if (info.isDirectory) {
|
|
6117
|
+
tree.directories.add(archivePath);
|
|
6118
|
+
await walk(physical, archivePath);
|
|
6119
|
+
} else if (info.isFile) {
|
|
6120
|
+
tree.files.set(archivePath, { executable: ((info.mode ?? 0) & 73) !== 0 });
|
|
6121
|
+
if (archivePath.startsWith("bin/") && info.size <= EXT_TOOLS_LAUNCHER_LIMIT) {
|
|
6122
|
+
tree.launchers.set(archivePath, await host2.readFile(physical));
|
|
6123
|
+
}
|
|
6124
|
+
} else {
|
|
6125
|
+
errors.push(`${archivePath}: not a regular file`);
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
};
|
|
6129
|
+
await walk(realRoot, "");
|
|
6130
|
+
return { tree, errors };
|
|
6131
|
+
}
|
|
6132
|
+
async function extToolsChecks(app, pikg, metaDir, catalogDir) {
|
|
6133
|
+
const checks = [];
|
|
6134
|
+
const platforms = /* @__PURE__ */ new Map();
|
|
6135
|
+
const stray = [];
|
|
6136
|
+
for (const [key, subpackage] of Object.entries(pikg.sub_pkgs)) {
|
|
6137
|
+
const platform = extToolsPlatformOfKey(key);
|
|
6138
|
+
if (!platform || subpackage.source.type !== "path") {
|
|
6139
|
+
stray.push(key);
|
|
6140
|
+
continue;
|
|
6141
|
+
}
|
|
6142
|
+
platforms.set(platform.name, resolve$1(metaDir, subpackage.source.path));
|
|
6143
|
+
}
|
|
6144
|
+
const sources = [...platforms.values()];
|
|
6145
|
+
const source = sources.length > 0 ? dirname(dirname(sources[0])) : dirname(metaDir);
|
|
6146
|
+
const master = await readExtToolsManifestFile(join(source, EXT_TOOLS_MANIFEST_FILE));
|
|
6147
|
+
const manifestErrors = [...master.errors];
|
|
6148
|
+
if (stray.length > 0)
|
|
6149
|
+
manifestErrors.push(`sub packages must be ext_tools_<os>_<arch> from a directory: ${stray.join(", ")}`);
|
|
6150
|
+
const manifest = master.manifest;
|
|
6151
|
+
if (manifest) {
|
|
6152
|
+
if (manifest.id !== app.name)
|
|
6153
|
+
manifestErrors.push(`id ${manifest.id} is not the App name ${app.name}`);
|
|
6154
|
+
if (manifest.version !== app.version)
|
|
6155
|
+
manifestErrors.push(`version ${manifest.version} is not the App version ${app.version}`);
|
|
6156
|
+
const without = manifest.platforms.filter((name) => !platforms.has(name));
|
|
6157
|
+
if (without.length > 0)
|
|
6158
|
+
manifestErrors.push(`platforms without a sub package: ${without.join(", ")}`);
|
|
6159
|
+
const unlisted = [...platforms.keys()].filter((name) => !manifest.platforms.includes(name));
|
|
6160
|
+
if (unlisted.length > 0)
|
|
6161
|
+
manifestErrors.push(`sub packages of platforms it does not list: ${unlisted.join(", ")}`);
|
|
6162
|
+
}
|
|
6163
|
+
checks.push({
|
|
6164
|
+
name: "ext_tools_manifest",
|
|
6165
|
+
status: manifestErrors.length === 0 ? "ok" : "error",
|
|
6166
|
+
path: join(source, EXT_TOOLS_MANIFEST_FILE),
|
|
6167
|
+
...manifestErrors.length > 0 ? { errors: manifestErrors } : {}
|
|
6168
|
+
});
|
|
6169
|
+
if (!manifest)
|
|
6170
|
+
return checks;
|
|
6171
|
+
for (const [platform, dir] of platforms) {
|
|
6172
|
+
const errors = [];
|
|
6173
|
+
try {
|
|
6174
|
+
const { tree, errors: walkErrors } = await readExtToolsPayloadTree(dir);
|
|
6175
|
+
errors.push(...walkErrors);
|
|
6176
|
+
const copy = await readExtToolsManifestFile(join(dir, EXT_TOOLS_MANIFEST_FILE));
|
|
6177
|
+
if (copy.errors.length > 0)
|
|
6178
|
+
errors.push(`${EXT_TOOLS_MANIFEST_FILE}: ${copy.errors.join("; ")}`);
|
|
6179
|
+
else if (copy.text !== master.text)
|
|
6180
|
+
errors.push(`${EXT_TOOLS_MANIFEST_FILE}: differs from the package's manifest`);
|
|
6181
|
+
errors.push(...extToolsPayloadErrors(manifest, platform, tree, [source, metaDir]));
|
|
6182
|
+
} catch (error) {
|
|
6183
|
+
if (!isHostError(error, "NotFound"))
|
|
6184
|
+
throw error;
|
|
6185
|
+
errors.push(`${dir}: not found`);
|
|
6186
|
+
}
|
|
6187
|
+
checks.push({
|
|
6188
|
+
name: "ext_tools_payload",
|
|
6189
|
+
platform,
|
|
6190
|
+
status: errors.length === 0 ? "ok" : "error",
|
|
6191
|
+
path: dir,
|
|
6192
|
+
...errors.length > 0 ? { errors } : {}
|
|
6193
|
+
});
|
|
6194
|
+
}
|
|
6195
|
+
if (!catalogDir) {
|
|
6196
|
+
checks.push({
|
|
6197
|
+
name: "ext_tools_catalog",
|
|
6198
|
+
status: "skipped",
|
|
6199
|
+
detail: "no --catalog: requires and commands other packages also provide are not checked"
|
|
6200
|
+
});
|
|
6201
|
+
return checks;
|
|
6202
|
+
}
|
|
6203
|
+
const others = /* @__PURE__ */ new Map();
|
|
6204
|
+
const catalogErrors = [];
|
|
6205
|
+
for (const entry of await getHost().readDir(catalogDir)) {
|
|
6206
|
+
if (entry.isFile)
|
|
6207
|
+
continue;
|
|
6208
|
+
const candidate = await readExtToolsManifestFile(join(catalogDir, entry.name, EXT_TOOLS_MANIFEST_FILE));
|
|
6209
|
+
if (candidate.errors.includes("not found"))
|
|
6210
|
+
continue;
|
|
6211
|
+
if (!candidate.manifest)
|
|
6212
|
+
catalogErrors.push(`${entry.name}: ${candidate.errors.join("; ")}`);
|
|
6213
|
+
else if (candidate.manifest.id !== manifest.id)
|
|
6214
|
+
others.set(candidate.manifest.id, candidate.manifest);
|
|
6215
|
+
}
|
|
6216
|
+
for (const required of manifest.requires) {
|
|
6217
|
+
if (!others.has(required.id))
|
|
6218
|
+
catalogErrors.push(`requires ${required.id}: not in the catalog`);
|
|
6219
|
+
}
|
|
6220
|
+
const commands = new Set(manifest.provides.map((provided) => provided.command));
|
|
6221
|
+
for (const other of others.values()) {
|
|
6222
|
+
if (manifest.conflicts.includes(other.id) || other.conflicts.includes(manifest.id))
|
|
6223
|
+
continue;
|
|
6224
|
+
const shared = other.provides.map((provided) => provided.command).filter((command) => commands.has(command));
|
|
6225
|
+
if (shared.length > 0)
|
|
6226
|
+
catalogErrors.push(`${other.id} also provides ${shared.join(", ")}: one of the two must list the other in conflicts`);
|
|
6227
|
+
}
|
|
6228
|
+
checks.push({
|
|
6229
|
+
name: "ext_tools_catalog",
|
|
6230
|
+
status: catalogErrors.length === 0 ? "ok" : "error",
|
|
6231
|
+
path: catalogDir,
|
|
6232
|
+
packages: [...others.keys()],
|
|
6233
|
+
...catalogErrors.length > 0 ? { errors: catalogErrors } : {}
|
|
6234
|
+
});
|
|
6235
|
+
return checks;
|
|
6236
|
+
}
|
|
5699
6237
|
async function buildCommand$1(ctx, input, docker, now) {
|
|
5700
6238
|
const metaInput = optionalInputString(input, "meta_dir") ?? "./dapp_meta";
|
|
5701
6239
|
const metaDir = await realDirectory(resolveFromCwd$1(ctx, metaInput), "dapp_meta");
|
|
@@ -7131,7 +7669,7 @@ function publishCommand() {
|
|
|
7131
7669
|
property: "app_type",
|
|
7132
7670
|
description: "App type; defaults to the template app_type",
|
|
7133
7671
|
type: "string",
|
|
7134
|
-
enum: ["dapp", "web", "agent"]
|
|
7672
|
+
enum: ["dapp", "web", "agent", "ext_tools"]
|
|
7135
7673
|
}
|
|
7136
7674
|
],
|
|
7137
7675
|
inputSchema: {
|
|
@@ -7139,7 +7677,7 @@ function publishCommand() {
|
|
|
7139
7677
|
properties: {
|
|
7140
7678
|
local_dir: { type: "string", minLength: 1 },
|
|
7141
7679
|
app_doc: { type: "string", minLength: 1 },
|
|
7142
|
-
app_type: { type: "string", enum: ["dapp", "web", "agent"] }
|
|
7680
|
+
app_type: { type: "string", enum: ["dapp", "web", "agent", "ext_tools"] }
|
|
7143
7681
|
},
|
|
7144
7682
|
required: ["local_dir", "app_doc"],
|
|
7145
7683
|
additionalProperties: false
|
|
@@ -7151,7 +7689,8 @@ function publishCommand() {
|
|
|
7151
7689
|
requiresSession: true,
|
|
7152
7690
|
examples: [
|
|
7153
7691
|
"buckyos app publish /srv/build/demo-web --app-doc ./demo-web.appdoc.json",
|
|
7154
|
-
"buckyos app publish /srv/build/demo-agent --app-doc ./demo-agent.appdoc.json --app-type agent"
|
|
7692
|
+
"buckyos app publish /srv/build/demo-agent --app-doc ./demo-agent.appdoc.json --app-type agent",
|
|
7693
|
+
"buckyos app publish /srv/build/ext-media --app-doc ./dapp_dist/APPDOC.json"
|
|
7155
7694
|
],
|
|
7156
7695
|
handler: async (ctx, input) => {
|
|
7157
7696
|
const localDir = expectString(input, "local_dir");
|
|
@@ -7227,6 +7766,12 @@ async function installApp(ctx, input, dependencies) {
|
|
|
7227
7766
|
try {
|
|
7228
7767
|
let inspection = submittedPlan ? await recomputePlan(ctx, source, input, submittedPlan) : await inspectSource(ctx, source, input);
|
|
7229
7768
|
verifyPikgBinding(source, submittedPlan ?? inspection.plan);
|
|
7769
|
+
if (inspection.plan.app_doc?.app_type === "ext_tools") {
|
|
7770
|
+
throw new ToolError(
|
|
7771
|
+
"EXT_TOOLS_NOT_AN_APP",
|
|
7772
|
+
"an extension tool package is not installed as an App: publish it to the Repo with `buckyos app publish`, then enable it for the Zone on the management page"
|
|
7773
|
+
);
|
|
7774
|
+
}
|
|
7230
7775
|
const app = expectObject$1(inspection.plan.app, "inspection.plan.app");
|
|
7231
7776
|
const appDid = expectString(app, "did");
|
|
7232
7777
|
const plannedOwner = submittedPlan ? expectString(submittedPlan, "owner_user_id") : void 0;
|