buckyos 0.7.159 → 0.7.160

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 CHANGED
@@ -2,7 +2,7 @@ import process from "node:process";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import * as nodePath from "node:path";
4
4
  import { resolve as resolve$5, dirname as dirname$3 } from "node:path";
5
- import { BuckyOSSDK, RuntimeType, buckyos, namelib, VerifyHubClient, createAppInstanceId, parseSessionTokenClaims, ndn, taskMgrTaskEventPath, ndm_proxy, objectUrl as objectUrl$1, ExecutorCommandStream } from "buckyos/node";
5
+ import { BuckyOSSDK, RuntimeType, buckyos, namelib, VerifyHubClient, createAppInstanceId, parseSessionTokenClaims, ndn, verifyBundle, controlSkeleton, aiwsContentDigest, AIWS_MANIFEST_VERSION, AIWS_MANIFEST_FILE, taskMgrTaskEventPath, ndm_proxy, objectUrl as objectUrl$1, ExecutorCommandStream, watchControl, buildControl, pullControl, exportBundle, NfspClient, AiwsSourceError } from "buckyos/node";
6
6
  import * as provisionSdk from "buckyos/provision";
7
7
  import { createHash } from "node:crypto";
8
8
  import { readFile, writeFile, readdir, stat, lstat, realpath, mkdtemp, rename, rm, mkdir, chmod, symlink, copyFile, open } from "node:fs/promises";
@@ -866,8 +866,8 @@ async function resolveConfig(explicit, environment2 = readEnvironment(), options
866
866
  const timeoutMs = explicit.timeout ? parseDuration(explicit.timeout) : DEFAULT_TIMEOUT_MS;
867
867
  if (endpoint)
868
868
  validateEndpoint(endpoint);
869
- const sessionToken = explicit.sessionToken ?? environment2.BUCKYOS_APPCLIENT_SESSION_TOKEN;
870
- if (sessionToken)
869
+ const sessionToken2 = explicit.sessionToken ?? environment2.BUCKYOS_APPCLIENT_SESSION_TOKEN;
870
+ if (sessionToken2)
871
871
  sources.session_token = explicit.sessionToken ? "argument" : "environment";
872
872
  if (profileName) {
873
873
  sources.profile = explicit.profile ? "argument" : environment2.BUCKYOS_TOOL_PROFILE ? "environment" : "config";
@@ -880,7 +880,7 @@ async function resolveConfig(explicit, environment2 = readEnvironment(), options
880
880
  zone,
881
881
  endpoint,
882
882
  identity,
883
- sessionToken,
883
+ sessionToken: sessionToken2,
884
884
  sessionTokenFile: explicit.sessionTokenFile,
885
885
  output,
886
886
  defaultProtocol,
@@ -4164,6 +4164,7 @@ function derivedSelector(key) {
4164
4164
  agent: {},
4165
4165
  agent_skills: {},
4166
4166
  agent_tools: {},
4167
+ aiws: {},
4167
4168
  script: {}
4168
4169
  };
4169
4170
  return selectors[key];
@@ -4482,6 +4483,24 @@ function validateServiceConfigTips(value, label) {
4482
4483
  function appDocObjectId(value) {
4483
4484
  return ndn.buildNamedObjectByJson("appdoc", value)[0].toString();
4484
4485
  }
4486
+ const AIWS_SUB_PACKAGE = "aiws";
4487
+ const MAX_AIWS_PAYLOAD_BYTES = 256 * 1024 * 1024;
4488
+ async function verifyAiwsPayload(tarGz) {
4489
+ const files = /* @__PURE__ */ new Map();
4490
+ for (const entry of await readTarGz(tarGz, MAX_AIWS_PAYLOAD_BYTES))
4491
+ files.set(entry.path, entry.bytes);
4492
+ let checked;
4493
+ try {
4494
+ checked = await verifyBundle(files);
4495
+ } catch (error) {
4496
+ throw invalid("aiws", `aiws bundle: ${error instanceof Error ? error.message : String(error)}`);
4497
+ }
4498
+ const errors = checked.diagnostics.filter((d) => d.severity === "error");
4499
+ if (errors.length > 0) {
4500
+ throw invalid("aiws", `aiws bundle: ${errors.map((d) => `${d.file}: ${d.message}`).join("; ")}`);
4501
+ }
4502
+ return { manifest_version: checked.manifest.manifest_version, controls: checked.manifest.controls.map((c) => ({ ident: c.ident, kind: c.kind, version: c.version, digest: c.digest })) };
4503
+ }
4485
4504
  async function validateSnapshot(distDir, appDoc, packageMeta) {
4486
4505
  const payloads = /* @__PURE__ */ new Map();
4487
4506
  for (const entryValue of Object.values(packageMeta.content_index)) {
@@ -4490,7 +4509,12 @@ async function validateSnapshot(distDir, appDoc, packageMeta) {
4490
4509
  const digest = await digestFile(path);
4491
4510
  payloads.set(entry.path, digest);
4492
4511
  }
4493
- return validateObjectGraph(appDoc, packageMeta, payloads, true, false, true);
4512
+ const validated = validateObjectGraph(appDoc, packageMeta, payloads, true, false, true);
4513
+ if (appDoc.app_type === "aiws") {
4514
+ for (const sub of validated.subpackages)
4515
+ await verifyAiwsPayload(await getHost().readFile(join$1(distDir, sub.payload_path)));
4516
+ }
4517
+ return validated;
4494
4518
  }
4495
4519
  async function packSnapshot(distDir, destination, appDoc, packageMeta) {
4496
4520
  const validated = await validateSnapshot(distDir, appDoc, packageMeta);
@@ -4595,9 +4619,15 @@ async function inspectPikg(path) {
4595
4619
  Boolean(jwtEntry),
4596
4620
  !jsonDoc || !jwtDoc || appDocObjectId(jsonDoc) === appDocObjectId(jwtDoc)
4597
4621
  );
4622
+ let aiws2;
4623
+ if (appDoc.app_type === "aiws") {
4624
+ const sub = validated.subpackages[0];
4625
+ aiws2 = await verifyAiwsPayload(await readZipEntry(archive, archive.byName.get(sub.payload_path), MAX_AIWS_PAYLOAD_BYTES));
4626
+ }
4598
4627
  const pikgDigest = await digestFile(path);
4599
4628
  return {
4600
4629
  schema_version: 1,
4630
+ ...aiws2 ? { aiws: aiws2 } : {},
4601
4631
  protocol: PACKAGE_META_SCHEMA,
4602
4632
  pikg_path: resolve$2(path),
4603
4633
  size: pikgDigest.size,
@@ -4894,7 +4924,7 @@ function validateAppDocShape(appDoc) {
4894
4924
  }
4895
4925
  if (appDoc.doc_type !== "app")
4896
4926
  throw invalid("appdoc", "APPDOC.doc_type must be app");
4897
- if (!["service", "dapp", "web", "agent", "agent_skills"].includes(String(appDoc.app_type))) {
4927
+ if (!["service", "dapp", "web", "agent", "agent_skills", "aiws"].includes(String(appDoc.app_type))) {
4898
4928
  throw invalid("appdoc", "APPDOC.app_type is invalid");
4899
4929
  }
4900
4930
  if (appDoc.name !== void 0)
@@ -4952,6 +4982,9 @@ function validateAppDocShape(appDoc) {
4952
4982
  throw invalid("appdoc", "an agent_skills App must contain only the agent_skills sub package");
4953
4983
  }
4954
4984
  }
4985
+ if (appDoc.app_type === "aiws" && Object.keys(pkgList).join() !== AIWS_SUB_PACKAGE) {
4986
+ throw invalid("appdoc", "an aiws package must contain only the aiws sub package");
4987
+ }
4955
4988
  deriveNamespaceForPackage(appDoc);
4956
4989
  }
4957
4990
  function deriveNamespaceForPackage(appDoc) {
@@ -5205,7 +5238,7 @@ function validateContentHandlers(value, permissions) {
5205
5238
  }
5206
5239
  return value;
5207
5240
  }
5208
- const PACKAGE_VERSION = "0.7.159";
5241
+ const PACKAGE_VERSION = "0.7.160";
5209
5242
  const TOOL_VERSION = PACKAGE_VERSION;
5210
5243
  const SDK_VERSION = PACKAGE_VERSION;
5211
5244
  const PROTOCOL_VERSION = "1";
@@ -5223,11 +5256,13 @@ const SHA256_ID = /^sha256:[0-9a-f]{64}$/;
5223
5256
  const OBJECT_ID = /^(?:appdoc|pkg):[0-9a-f]{64}$/;
5224
5257
  const SAFE_GENERATED_FILE = /^[A-Za-z0-9._-]+$/;
5225
5258
  const OBJECT_OUTPUT$c = { type: "object", additionalProperties: true };
5226
- const APP_KINDS = ["static-web", "script", "docker", "agent-skills"];
5259
+ const APP_KINDS = ["static-web", "script", "docker", "agent-skills", "aiws-controls"];
5227
5260
  const PATH_KINDS = {
5228
5261
  "static-web": { key: "web", category: "web", selectorType: "static" },
5229
5262
  script: { key: "script", category: "dapp", selectorType: "single" },
5230
- "agent-skills": { key: "agent_skills", category: "agent_skills", selectorType: "single" }
5263
+ "agent-skills": { key: "agent_skills", category: "agent_skills", selectorType: "single" },
5264
+ // AI Workspace controls: one bundle (aiws-manifest.json + controls/<dir>/), installed into the AI Workspace library
5265
+ "aiws-controls": { key: "aiws", category: "aiws", selectorType: "single" }
5231
5266
  };
5232
5267
  function packageEnvironmentQualifier(key, selector) {
5233
5268
  const effective = selector ?? derivedSelector(key);
@@ -5322,7 +5357,8 @@ function createPikgModule(dependencies = {}) {
5322
5357
  examples: [
5323
5358
  "buckyos pikg init .",
5324
5359
  "buckyos --non-interactive pikg init . --owner did:bns:root --kind static-web --source ./web/dist",
5325
- "buckyos --non-interactive pikg init . --owner did:bns:root --kind agent-skills --source ./skills"
5360
+ "buckyos --non-interactive pikg init . --owner did:bns:root --kind agent-skills --source ./skills",
5361
+ "buckyos --non-interactive pikg init . --owner did:bns:alice --kind aiws-controls --source ./aiws"
5326
5362
  ],
5327
5363
  handler: async (ctx, input) => await initCommand(ctx, input, docker)
5328
5364
  },
@@ -5345,7 +5381,7 @@ function createPikgModule(dependencies = {}) {
5345
5381
  execution: "local",
5346
5382
  networkAccess: false,
5347
5383
  examples: ["buckyos pikg build ./dapp_meta"],
5348
- handler: async (ctx, input) => await buildCommand(ctx, input, docker, now)
5384
+ handler: async (ctx, input) => await buildCommand$1(ctx, input, docker, now)
5349
5385
  },
5350
5386
  {
5351
5387
  verb: "pack",
@@ -5562,11 +5598,15 @@ async function initCommand(ctx, input, docker) {
5562
5598
  try {
5563
5599
  await getHost().lstat(absoluteSource);
5564
5600
  } catch (error) {
5565
- if (isHostError(error, "NotFound")) {
5601
+ if (!isHostError(error, "NotFound"))
5602
+ throw error;
5603
+ if (kind === "aiws-controls") {
5604
+ await writeAiwsSkeleton(absoluteSource, owner, name);
5605
+ sourceSummary2.skeleton = true;
5606
+ } else {
5566
5607
  await ctx.io.stderr(`warning: source does not exist yet: ${sourceSummary2.path}
5567
5608
  `);
5568
- } else
5569
- throw error;
5609
+ }
5570
5610
  }
5571
5611
  }
5572
5612
  const appMeta = {
@@ -5626,7 +5666,25 @@ async function initCommand(ctx, input, docker) {
5626
5666
  next_command: `buckyos pikg build ${metaDir}`
5627
5667
  };
5628
5668
  }
5629
- async function buildCommand(ctx, input, docker, now) {
5669
+ async function writeAiwsSkeleton(dir, ownerDid, name) {
5670
+ const files = controlSkeleton({ authorDid: ownerDid, name, title: name });
5671
+ const encoder = new TextEncoder();
5672
+ const host2 = getHost();
5673
+ await host2.mkdir(join(dir, "controls", name), { recursive: true });
5674
+ for (const [file, text2] of Object.entries(files))
5675
+ await host2.writeTextFile(join(dir, "controls", name, file), text2);
5676
+ const digest = await aiwsContentDigest(Object.entries(files).map(([file, text2]) => [file, encoder.encode(text2)]));
5677
+ const manifest = {
5678
+ manifest_version: AIWS_MANIFEST_VERSION,
5679
+ purpose: "aiws",
5680
+ host_api: { declarative: 1 },
5681
+ controls: [{ ident: `${ownerDid}#${name}`, name, kind: "declarative", version: 1, digest, dir: `controls/${name}/`, title: name }],
5682
+ workspace_templates: []
5683
+ };
5684
+ await host2.writeTextFile(join(dir, AIWS_MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
5685
+ `);
5686
+ }
5687
+ async function buildCommand$1(ctx, input, docker, now) {
5630
5688
  const metaInput = optionalInputString(input, "meta_dir") ?? "./dapp_meta";
5631
5689
  const metaDir = await realDirectory(resolveFromCwd$1(ctx, metaInput), "dapp_meta");
5632
5690
  const appMeta = parseAppMeta(await readJson(join(metaDir, "app.json"), "app.json"));
@@ -11098,7 +11156,12 @@ async function aiws(ctx, method, params) {
11098
11156
  const error = expectObject$4(result.error, `${method}.error`);
11099
11157
  const code = typeof error.code === "string" ? error.code : "AIWS_ERROR";
11100
11158
  const detail = typeof error.detail === "string" ? error.detail : code;
11101
- throw new ToolError(code, `${code}${typeof error.sub_code === "string" ? `/${error.sub_code}` : ""}: ${detail}`);
11159
+ const details = {};
11160
+ if (typeof error.sub_code === "string")
11161
+ details.sub_code = error.sub_code;
11162
+ if (error.data !== void 0)
11163
+ details.data = error.data;
11164
+ throw new ToolError(code, `${code}${typeof error.sub_code === "string" ? `/${error.sub_code}` : ""}: ${detail}`, void 0, false, details);
11102
11165
  }
11103
11166
  return result;
11104
11167
  }
@@ -11471,11 +11534,326 @@ const exportCommand = {
11471
11534
  return { ok: true, format: result.format, kind: result.kind, path: out };
11472
11535
  }
11473
11536
  };
11537
+ async function sessionToken(ctx) {
11538
+ const session = ctx.session;
11539
+ if (!session)
11540
+ throw new ToolError("AUTH_REQUIRED", "authenticated session is required", EXIT_AUTH);
11541
+ return (await session.ensureValid()).token;
11542
+ }
11543
+ async function openHome(ctx) {
11544
+ const session = ctx.session;
11545
+ if (!session)
11546
+ throw new ToolError("AUTH_REQUIRED", "authenticated session is required", EXIT_AUTH);
11547
+ const nfsp = new NfspClient({ baseUrl: gatewayOrigin$2(ctx.connection.endpoint), sessionToken: async () => (await session.ensureValid()).token });
11548
+ await nfsp.hello(["cli"]);
11549
+ return nfsp;
11550
+ }
11551
+ function extRpc(ctx) {
11552
+ return {
11553
+ extBuild: (workspace_id, params) => aiws(ctx, "ext.build", { workspace_id, ...params }),
11554
+ extExport: (workspace_id, entity_ids, purpose = "package") => aiws(ctx, "ext.export", { workspace_id, entity_ids, purpose })
11555
+ };
11556
+ }
11557
+ function assetFetcher(ctx) {
11558
+ return async (workspaceId, objId) => {
11559
+ const response = await fetch(`${gatewayOrigin$2(ctx.connection.endpoint)}/kapi/aiworkspace/asset/${encodeURIComponent(workspaceId)}/${encodeURIComponent(objId)}`, {
11560
+ headers: { Authorization: `Bearer ${await sessionToken(ctx)}` },
11561
+ signal: ctx.signal
11562
+ });
11563
+ if (!response.ok)
11564
+ throw new ToolError("AIWS_ASSET_UNAVAILABLE", `asset ${objId}: HTTP ${response.status}`);
11565
+ return new Uint8Array(await response.arrayBuffer());
11566
+ };
11567
+ }
11568
+ async function sourceErrors(run) {
11569
+ try {
11570
+ return await run();
11571
+ } catch (error) {
11572
+ if (error instanceof AiwsSourceError)
11573
+ throw new ToolError("BUILD_FAILED", `BUILD_FAILED: ${error.message}`, void 0, false, { data: { diagnostics: error.diagnostics } });
11574
+ throw error;
11575
+ }
11576
+ }
11577
+ function list(input, key) {
11578
+ return requiredInputString(input, key).split(",").map((s) => s.trim()).filter(Boolean);
11579
+ }
11580
+ const LOCAL_STATE = ".aiws-build.json";
11581
+ async function isLocalDir(path) {
11582
+ try {
11583
+ return (await getHost().stat(path)).isDirectory;
11584
+ } catch {
11585
+ return false;
11586
+ }
11587
+ }
11588
+ async function localFiles(dir) {
11589
+ const host2 = getHost();
11590
+ const files = [];
11591
+ const problems = [];
11592
+ let total = 0;
11593
+ for (const entry of await host2.readDir(dir)) {
11594
+ if (entry.name.startsWith("."))
11595
+ continue;
11596
+ if (!entry.isFile) {
11597
+ problems.push({ file: entry.name, severity: "error", message: "a control source folder is flat: move files out of sub-folders" });
11598
+ continue;
11599
+ }
11600
+ const bytes = await host2.readFile(host2.path.join(dir, entry.name));
11601
+ total += bytes.length;
11602
+ files.push({ name: entry.name, base64: btoa(Array.from(bytes, (b) => String.fromCharCode(b)).join("")) });
11603
+ }
11604
+ if (files.length === 0)
11605
+ problems.push({ file: "", severity: "error", message: `${dir} holds no files` });
11606
+ if (total > 4 * 1024 * 1024)
11607
+ problems.push({ file: "", severity: "error", message: "a control is limited to 4 MiB per build from a local folder" });
11608
+ if (problems.length > 0)
11609
+ throw new ToolError("BUILD_FAILED", `BUILD_FAILED: ${problems.map((p) => `${p.file}: ${p.message}`).join("; ")}`, void 0, false, { data: { diagnostics: problems } });
11610
+ return files.sort((a, b) => a.name.localeCompare(b.name));
11611
+ }
11612
+ async function localState(dir) {
11613
+ try {
11614
+ return JSON.parse(await getHost().readTextFile(getHost().path.join(dir, LOCAL_STATE)));
11615
+ } catch {
11616
+ return null;
11617
+ }
11618
+ }
11619
+ async function buildLocal(ctx, ws, dir, opts) {
11620
+ const state = await localState(dir);
11621
+ const result = await aiws(ctx, "ext.build", {
11622
+ workspace_id: ws,
11623
+ files: await localFiles(dir),
11624
+ ...opts.folderId ? { folder_id: opts.folderId } : {},
11625
+ ...state && state.workspace_id === ws ? { expect: { def_rev: state.def_rev, dir_rev: state.dir_rev } } : {},
11626
+ ...opts.force ? { force: true } : {},
11627
+ ...opts.dryRun ? { dry_run: true } : {}
11628
+ });
11629
+ if (!opts.dryRun && typeof result.def_entity_id === "string" && typeof result.def_rev === "number") {
11630
+ await getHost().writeTextFile(getHost().path.join(dir, LOCAL_STATE), `${JSON.stringify({ workspace_id: ws, def_entity_id: result.def_entity_id, def_rev: result.def_rev, dir_rev: result.dir_rev ?? 0, ident: result.ident, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
11631
+ `);
11632
+ }
11633
+ return result;
11634
+ }
11635
+ const buildCommand = {
11636
+ verb: "build",
11637
+ summary: "Build a control from its source folder (local, or in the Zone; component.json + files) into a Workspace; instances update, a failure commits nothing",
11638
+ description: "The folder is a local folder (when it exists here; its files are sent inline, ≤ 4 MiB) or a Zone path (e.g. /home/alice/AI Workspace/Demo/controls/kpi-card; --zone-path forces it), flat. The base of the last build or pull is kept in its .aiws-build.json; building over a newer Workspace version is refused (pull first, or --force). --watch rebuilds on every change until interrupted.",
11639
+ positionals: [{ name: "dir", description: "Local folder, or Zone path of the control source folder", required: true }],
11640
+ options: [
11641
+ WORKSPACE_OPTION,
11642
+ { name: "folder", property: "folder_id", description: "Data folder for the definition (default: data/extensions)", type: "string" },
11643
+ { name: "watch", property: "watch", description: "Rebuild whenever the folder changes", type: "boolean" },
11644
+ { name: "dry-run", property: "dry_run", description: "Check only: diagnostics, nothing committed", type: "boolean" },
11645
+ { name: "force", property: "force", description: "Build over whatever the Workspace holds now", type: "boolean" },
11646
+ { name: "zone-path", property: "zone_path", description: "Treat the folder as a Zone path even if a local folder has that name", type: "boolean" }
11647
+ ],
11648
+ inputSchema: workspaceInput({ dir: { type: "string", minLength: 1 }, folder_id: { type: "string" }, watch: { type: "boolean" }, dry_run: { type: "boolean" }, force: { type: "boolean" }, zone_path: { type: "boolean" } }, ["dir"]),
11649
+ outputSchema: OBJECT_OUTPUT,
11650
+ resultSchemaVersion: 1,
11651
+ access: { mode: "fixed", level: "write" },
11652
+ asyncMode: "sync",
11653
+ requiresSession: true,
11654
+ examples: ['buckyos aiws build "/home/alice/AI Workspace/Demo/controls/kpi-card" --workspace ws_abc', 'buckyos aiws build "/home/alice/AI Workspace/Demo/controls/kpi-card" --workspace ws_abc --watch'],
11655
+ handler: async (ctx, input) => {
11656
+ const ws = requiredInputString(input, "workspace_id");
11657
+ const dir = requiredInputString(input, "dir");
11658
+ const opts = { folderId: inputString(input, "folder_id"), force: input.force === true, dryRun: input.dry_run === true };
11659
+ if (input.zone_path !== true && await isLocalDir(getHost().path.resolve(ctx.cwd, dir))) {
11660
+ const local = getHost().path.resolve(ctx.cwd, dir);
11661
+ if (input.watch === true)
11662
+ throw new UsageError("AIWS_USAGE", "--watch follows a Zone folder; rebuild a local folder yourself");
11663
+ return buildLocal(ctx, ws, local, opts);
11664
+ }
11665
+ const nfsp = await openHome(ctx);
11666
+ if (input.watch !== true)
11667
+ return sourceErrors(() => buildControl(extRpc(ctx), nfsp, ws, dir, opts));
11668
+ let builds = 0;
11669
+ let failures = 0;
11670
+ await watchControl(nfsp, dir, async () => {
11671
+ try {
11672
+ const r = await sourceErrors(() => buildControl(extRpc(ctx), nfsp, ws, dir, opts));
11673
+ builds++;
11674
+ await ctx.io.stderr(`built ${r.ident} v${r.version ?? "?"} (${r.unchanged ? "unchanged" : r.status})
11675
+ `);
11676
+ } catch (error) {
11677
+ failures++;
11678
+ const diagnostics = error instanceof ToolError ? error.details.data?.diagnostics : void 0;
11679
+ await ctx.io.stderr(`build failed: ${error instanceof Error ? error.message : String(error)}
11680
+ ${diagnostics ? `${JSON.stringify(diagnostics, null, 2)}
11681
+ ` : ""}`);
11682
+ }
11683
+ }, { signal: ctx.signal });
11684
+ return { ok: true, watched: dir, builds, failures };
11685
+ }
11686
+ };
11687
+ const pullCommand = {
11688
+ verb: "pull",
11689
+ summary: "Write a control definition’s Workspace snapshot into a folder — a Zone path (default) or a local one (--local) — as the base of the next build",
11690
+ positionals: [{ name: "def", description: "Definition (buckyos.block-def) id", required: true }],
11691
+ options: [WORKSPACE_OPTION, { name: "out", property: "out", description: "Folder to write (created)", type: "string", required: true }, { name: "local", property: "local", description: "Write into a local folder instead of the Zone", type: "boolean" }],
11692
+ inputSchema: workspaceInput({ def: { type: "string", minLength: 1 }, out: { type: "string", minLength: 1 }, local: { type: "boolean" } }, ["def", "out"]),
11693
+ outputSchema: OBJECT_OUTPUT,
11694
+ resultSchemaVersion: 1,
11695
+ access: { mode: "fixed", level: "write" },
11696
+ asyncMode: "sync",
11697
+ requiresSession: true,
11698
+ examples: ['buckyos aiws pull def_abc --workspace ws_abc --out "/home/alice/AI Workspace/Demo/controls/kpi-card"'],
11699
+ handler: async (ctx, input) => {
11700
+ if (input.local === true) {
11701
+ const ws = requiredInputString(input, "workspace_id");
11702
+ const host2 = getHost();
11703
+ const out = host2.path.resolve(ctx.cwd, requiredInputString(input, "out"));
11704
+ const r2 = await aiws(ctx, "ext.export", { workspace_id: ws, entity_ids: [requiredInputString(input, "def")], purpose: "pull" });
11705
+ const control = expectObject$4(r2.controls[0], "ext.export.controls[0]");
11706
+ await host2.mkdir(out, { recursive: true });
11707
+ const fetchAsset = assetFetcher(ctx);
11708
+ const files = [];
11709
+ for (const f of control.files) {
11710
+ const target = host2.path.join(out, f.name);
11711
+ if (f.text !== void 0)
11712
+ await host2.writeTextFile(target, f.text);
11713
+ else
11714
+ await host2.writeFile(target, await fetchAsset(ws, String(f.obj_id)));
11715
+ files.push(f.name);
11716
+ }
11717
+ await host2.writeTextFile(host2.path.join(out, LOCAL_STATE), `${JSON.stringify({ workspace_id: ws, def_entity_id: control.entity_id, def_rev: control.def_rev, dir_rev: control.dir_rev, ident: control.ident, at: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
11718
+ `);
11719
+ return { ok: true, out, files };
11720
+ }
11721
+ const nfsp = await openHome(ctx);
11722
+ const r = await pullControl(extRpc(ctx), nfsp, requiredInputString(input, "workspace_id"), requiredInputString(input, "def"), requiredInputString(input, "out"), assetFetcher(ctx));
11723
+ return { ok: true, out: requiredInputString(input, "out"), ...r };
11724
+ }
11725
+ };
11726
+ async function pikgVerb(ctx, verb, input) {
11727
+ const command = createPikgModule().commands.find((c) => c.verb === verb);
11728
+ if (!command)
11729
+ throw new ToolError("INTERNAL", `pikg ${verb} is missing`);
11730
+ return expectObject$4(await command.handler(ctx, input), `pikg ${verb}`);
11731
+ }
11732
+ async function uploadPackage(ctx, path) {
11733
+ const bucket_id = `user/aiworkspace/${ctx.principal.id}/exports`;
11734
+ const key = `aiws/${Date.now()}-${getHost().path.basename(path)}`;
11735
+ const bytes = await getHost().readFile(path);
11736
+ if (bytes.byteLength > 32 * 1024 * 1024)
11737
+ throw new UsageError("FILE_TOO_LARGE", "a control package installed from the CLI is limited to 32 MiB");
11738
+ const url = `${gatewayOrigin$2(ctx.connection.endpoint)}/bucket/v1/${bucket_id}/${key.split("/").map(encodeURIComponent).join("/")}`;
11739
+ const response = await fetch(url, { method: "PUT", headers: { Authorization: `Bearer ${await sessionToken(ctx)}` }, body: new Blob([bytes]), signal: ctx.signal });
11740
+ if (!response.ok)
11741
+ throw new ToolError("AIWS_UPLOAD_FAILED", `upload of ${path}: HTTP ${response.status} ${await response.text().catch(() => "")}`);
11742
+ return { bucket_id, key };
11743
+ }
11744
+ const extExportCommand = {
11745
+ verb: "ext-export",
11746
+ summary: "Export Workspace controls as a bundle in a local folder; optionally pack it as an aiws PIKG and install it into your library",
11747
+ description: "Writes <out>/aiws/ (aiws-manifest.json + controls/<dir>/). --pikg (or --install) also runs pikg init/build/pack in <out> (kind aiws-controls); --install binds the .pikg into your aiworkspace exports bucket and calls ext.install.",
11748
+ positionals: [{ name: "defs", description: "Definition (or instance) ids, comma-separated", required: true }],
11749
+ options: [
11750
+ WORKSPACE_OPTION,
11751
+ { name: "out", property: "out", description: "Local folder to write into (created)", type: "string", required: true },
11752
+ { name: "name", property: "name", description: "Package name (default: the first control’s)", type: "string" },
11753
+ { name: "version", property: "version", description: "Package version (default 0.1.0)", type: "string" },
11754
+ { name: "owner", property: "owner", description: "Owner DID of the package (default did:bns:<you>)", type: "string" },
11755
+ { name: "pikg", property: "pikg", description: "Also pack the bundle as an aiws PIKG", type: "boolean" },
11756
+ { name: "install", property: "install", description: "Pack, then install into your AI Workspace library (dev mode)", type: "boolean" }
11757
+ ],
11758
+ inputSchema: workspaceInput({ defs: { type: "string", minLength: 1 }, out: { type: "string", minLength: 1 }, name: { type: "string" }, version: { type: "string" }, owner: { type: "string" }, pikg: { type: "boolean" }, install: { type: "boolean" } }, ["defs", "out"]),
11759
+ outputSchema: OBJECT_OUTPUT,
11760
+ resultSchemaVersion: 1,
11761
+ access: { mode: "fixed", level: "write" },
11762
+ asyncMode: "sync",
11763
+ requiresSession: true,
11764
+ examples: ["buckyos aiws ext-export def_abc,def_xyz --workspace ws_abc --out ./kpi-pack --install"],
11765
+ handler: async (ctx, input) => {
11766
+ const ws = requiredInputString(input, "workspace_id");
11767
+ const host2 = getHost();
11768
+ const out = host2.path.resolve(ctx.cwd, requiredInputString(input, "out"));
11769
+ const bundle = await exportBundle(extRpc(ctx), ws, list(input, "defs"), assetFetcher(ctx));
11770
+ const root = host2.path.join(out, "aiws");
11771
+ await host2.remove(root, { recursive: true }).catch(() => void 0);
11772
+ for (const [path, bytes] of bundle.files) {
11773
+ const target = host2.path.join(root, path);
11774
+ await host2.mkdir(host2.path.dirname(target), { recursive: true });
11775
+ await host2.writeFile(target, bytes);
11776
+ }
11777
+ const result = { ok: true, bundle: root, controls: bundle.manifest.controls.map((c) => ({ ident: c.ident, kind: c.kind, version: c.version, digest: c.digest })) };
11778
+ if (input.pikg !== true && input.install !== true)
11779
+ return result;
11780
+ const name = inputString(input, "name") ?? bundle.manifest.controls[0]?.name.replace(/[._]/g, "-") ?? "controls";
11781
+ const owner = inputString(input, "owner") ?? `did:bns:${ctx.principal.id}`;
11782
+ const version = inputString(input, "version") ?? "0.1.0";
11783
+ const appJson = host2.path.join(out, "dapp_meta", "app.json");
11784
+ const existing = await host2.readTextFile(appJson).then((t) => JSON.parse(t)).catch(() => null);
11785
+ if (!existing) {
11786
+ await pikgVerb(ctx, "init", { project_dir: out, name, owner, kind: "aiws-controls", source: "./aiws", version });
11787
+ } else if (inputString(input, "version") && existing.version !== version) {
11788
+ await host2.writeTextFile(appJson, `${JSON.stringify({ ...existing, version }, null, 2)}
11789
+ `);
11790
+ const pikgJson = host2.path.join(out, "dapp_meta", "pikg.json");
11791
+ const meta = JSON.parse(await host2.readTextFile(pikgJson));
11792
+ await host2.writeTextFile(pikgJson, `${JSON.stringify({ ...meta, pikg_file: `${existing.name}-${version}.pikg` }, null, 2)}
11793
+ `);
11794
+ }
11795
+ await pikgVerb(ctx, "build", { meta_dir: host2.path.join(out, "dapp_meta") });
11796
+ const packed = await pikgVerb(ctx, "pack", { dist_dir: host2.path.join(out, "dapp_dist") });
11797
+ const pikgPath = String(packed.pikg_path);
11798
+ result.pikg = pikgPath;
11799
+ if (input.install !== true)
11800
+ return result;
11801
+ result.install = await aiws(ctx, "ext.install", { pikg: await uploadPackage(ctx, pikgPath), mode: "dev" });
11802
+ return result;
11803
+ }
11804
+ };
11805
+ const libraryCommand = {
11806
+ verb: "library",
11807
+ summary: "Your AI Workspace control library: `list`, `install <file.pikg>` (an aiws package, dev mode), `remove <pkg_id>`",
11808
+ positionals: [{ name: "action", description: "list | install | remove", required: true }, { name: "target", description: "install: local .pikg path; remove: package id", required: false }],
11809
+ options: [],
11810
+ inputSchema: { type: "object", properties: { action: { type: "string", enum: ["list", "install", "remove"] }, target: { type: "string" } }, required: ["action"], additionalProperties: false },
11811
+ outputSchema: OBJECT_OUTPUT,
11812
+ resultSchemaVersion: 1,
11813
+ access: { mode: "fixed", level: "write" },
11814
+ asyncMode: "sync",
11815
+ requiresSession: true,
11816
+ examples: ["buckyos aiws library list", "buckyos aiws library install ./kpi-pack-0.1.0.pikg", "buckyos aiws library remove kpi-pack.alice.bns.did"],
11817
+ handler: async (ctx, input) => {
11818
+ const action = requiredInputString(input, "action");
11819
+ if (action === "list")
11820
+ return aiws(ctx, "ext.list_library", {});
11821
+ if (action === "remove")
11822
+ return aiws(ctx, "ext.uninstall", { pkg_id: requiredInputString(input, "target") });
11823
+ if (action !== "install")
11824
+ throw new UsageError("AIWS_USAGE", "library: list, install <file.pikg> or remove <pkg_id>");
11825
+ const path = getHost().path.resolve(ctx.cwd, requiredInputString(input, "target"));
11826
+ return aiws(ctx, "ext.install", { pikg: await uploadPackage(ctx, path), mode: "dev" });
11827
+ }
11828
+ };
11829
+ const extImportCommand = {
11830
+ verb: "ext-import",
11831
+ summary: "Bring controls of your library into a Workspace (one Commit): <pkg_id>#<ident>, comma-separated",
11832
+ positionals: [{ name: "items", description: "<pkg_id>#<author_did>#<name>[,…] (ext.list_library names them)", required: true }],
11833
+ options: [WORKSPACE_OPTION, { name: "folder", property: "folder_id", description: "Data folder for the definitions (default: data/extensions)", type: "string" }],
11834
+ inputSchema: workspaceInput({ items: { type: "string", minLength: 1 }, folder_id: { type: "string" } }, ["items"]),
11835
+ outputSchema: OBJECT_OUTPUT,
11836
+ resultSchemaVersion: 1,
11837
+ access: { mode: "fixed", level: "write" },
11838
+ asyncMode: "sync",
11839
+ requiresSession: true,
11840
+ examples: ["buckyos aiws ext-import kpi-pack.alice.bns.did#did:bns:alice#kpi-card --workspace ws_abc"],
11841
+ handler: (ctx, input) => {
11842
+ const items = list(input, "items").map((item) => {
11843
+ const cut = item.indexOf("#");
11844
+ if (cut <= 0)
11845
+ throw new UsageError("AIWS_USAGE", `${item}: <pkg_id>#<ident>`);
11846
+ return { pkg_id: item.slice(0, cut), ident: item.slice(cut + 1) };
11847
+ });
11848
+ const folder = inputString(input, "folder_id");
11849
+ return aiws(ctx, "ext.import", { workspace_id: requiredInputString(input, "workspace_id"), items, ...folder ? { folder_id: folder } : {} });
11850
+ }
11851
+ };
11474
11852
  function createAiwsModule() {
11475
11853
  return {
11476
11854
  name: "aiws",
11477
- summary: "AI Workspace: read a workspace, Block-controls (catalogue, import, export), and execute wish runs as their executor (claim, heartbeat, report)",
11478
- commands: [infoCommand, readCommand, queryCommand, controlsCommand, importCommand, exportCommand, runGetCommand, runClaimCommand, runHeartbeatCommand, runFilesCommand, runToolCommand, runProgramCommand, runReportCommand]
11855
+ summary: "AI Workspace: read a workspace, Block-controls (catalogue, import, export), extension controls (build, pull, export, library, import), and execute wish runs as their executor (claim, heartbeat, report)",
11856
+ commands: [infoCommand, readCommand, queryCommand, controlsCommand, importCommand, exportCommand, buildCommand, pullCommand, extExportCommand, libraryCommand, extImportCommand, runGetCommand, runClaimCommand, runHeartbeatCommand, runFilesCommand, runToolCommand, runProgramCommand, runReportCommand]
11479
11857
  };
11480
11858
  }
11481
11859
  const VERSION = TOOL_VERSION;