buckyos 0.7.159 → 0.7.162

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, packBuildFiles, templateDir, isSourcePath } 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,28 @@ 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 {
4503
+ manifest_version: checked.manifest.manifest_version,
4504
+ controls: checked.manifest.controls.map((c) => ({ ident: c.ident, kind: c.kind, version: c.version, digest: c.digest })),
4505
+ templates: checked.manifest.workspace_templates.map((t) => ({ id: t.id, title: t.title, digest: t.digest, requires: t.requires }))
4506
+ };
4507
+ }
4485
4508
  async function validateSnapshot(distDir, appDoc, packageMeta) {
4486
4509
  const payloads = /* @__PURE__ */ new Map();
4487
4510
  for (const entryValue of Object.values(packageMeta.content_index)) {
@@ -4490,7 +4513,12 @@ async function validateSnapshot(distDir, appDoc, packageMeta) {
4490
4513
  const digest = await digestFile(path);
4491
4514
  payloads.set(entry.path, digest);
4492
4515
  }
4493
- return validateObjectGraph(appDoc, packageMeta, payloads, true, false, true);
4516
+ const validated = validateObjectGraph(appDoc, packageMeta, payloads, true, false, true);
4517
+ if (appDoc.app_type === "aiws") {
4518
+ for (const sub of validated.subpackages)
4519
+ await verifyAiwsPayload(await getHost().readFile(join$1(distDir, sub.payload_path)));
4520
+ }
4521
+ return validated;
4494
4522
  }
4495
4523
  async function packSnapshot(distDir, destination, appDoc, packageMeta) {
4496
4524
  const validated = await validateSnapshot(distDir, appDoc, packageMeta);
@@ -4595,9 +4623,15 @@ async function inspectPikg(path) {
4595
4623
  Boolean(jwtEntry),
4596
4624
  !jsonDoc || !jwtDoc || appDocObjectId(jsonDoc) === appDocObjectId(jwtDoc)
4597
4625
  );
4626
+ let aiws2;
4627
+ if (appDoc.app_type === "aiws") {
4628
+ const sub = validated.subpackages[0];
4629
+ aiws2 = await verifyAiwsPayload(await readZipEntry(archive, archive.byName.get(sub.payload_path), MAX_AIWS_PAYLOAD_BYTES));
4630
+ }
4598
4631
  const pikgDigest = await digestFile(path);
4599
4632
  return {
4600
4633
  schema_version: 1,
4634
+ ...aiws2 ? { aiws: aiws2 } : {},
4601
4635
  protocol: PACKAGE_META_SCHEMA,
4602
4636
  pikg_path: resolve$2(path),
4603
4637
  size: pikgDigest.size,
@@ -4894,7 +4928,7 @@ function validateAppDocShape(appDoc) {
4894
4928
  }
4895
4929
  if (appDoc.doc_type !== "app")
4896
4930
  throw invalid("appdoc", "APPDOC.doc_type must be app");
4897
- if (!["service", "dapp", "web", "agent", "agent_skills"].includes(String(appDoc.app_type))) {
4931
+ if (!["service", "dapp", "web", "agent", "agent_skills", "aiws"].includes(String(appDoc.app_type))) {
4898
4932
  throw invalid("appdoc", "APPDOC.app_type is invalid");
4899
4933
  }
4900
4934
  if (appDoc.name !== void 0)
@@ -4952,6 +4986,9 @@ function validateAppDocShape(appDoc) {
4952
4986
  throw invalid("appdoc", "an agent_skills App must contain only the agent_skills sub package");
4953
4987
  }
4954
4988
  }
4989
+ if (appDoc.app_type === "aiws" && Object.keys(pkgList).join() !== AIWS_SUB_PACKAGE) {
4990
+ throw invalid("appdoc", "an aiws package must contain only the aiws sub package");
4991
+ }
4955
4992
  deriveNamespaceForPackage(appDoc);
4956
4993
  }
4957
4994
  function deriveNamespaceForPackage(appDoc) {
@@ -5205,7 +5242,7 @@ function validateContentHandlers(value, permissions) {
5205
5242
  }
5206
5243
  return value;
5207
5244
  }
5208
- const PACKAGE_VERSION = "0.7.159";
5245
+ const PACKAGE_VERSION = "0.7.162";
5209
5246
  const TOOL_VERSION = PACKAGE_VERSION;
5210
5247
  const SDK_VERSION = PACKAGE_VERSION;
5211
5248
  const PROTOCOL_VERSION = "1";
@@ -5223,11 +5260,13 @@ const SHA256_ID = /^sha256:[0-9a-f]{64}$/;
5223
5260
  const OBJECT_ID = /^(?:appdoc|pkg):[0-9a-f]{64}$/;
5224
5261
  const SAFE_GENERATED_FILE = /^[A-Za-z0-9._-]+$/;
5225
5262
  const OBJECT_OUTPUT$c = { type: "object", additionalProperties: true };
5226
- const APP_KINDS = ["static-web", "script", "docker", "agent-skills"];
5263
+ const APP_KINDS = ["static-web", "script", "docker", "agent-skills", "aiws-controls"];
5227
5264
  const PATH_KINDS = {
5228
5265
  "static-web": { key: "web", category: "web", selectorType: "static" },
5229
5266
  script: { key: "script", category: "dapp", selectorType: "single" },
5230
- "agent-skills": { key: "agent_skills", category: "agent_skills", selectorType: "single" }
5267
+ "agent-skills": { key: "agent_skills", category: "agent_skills", selectorType: "single" },
5268
+ // 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" }
5231
5270
  };
5232
5271
  function packageEnvironmentQualifier(key, selector) {
5233
5272
  const effective = selector ?? derivedSelector(key);
@@ -5297,7 +5336,12 @@ function createPikgModule(dependencies = {}) {
5297
5336
  type: "string"
5298
5337
  },
5299
5338
  { name: "version", description: "Initial App version", type: "string" },
5300
- { name: "app-did", property: "app_did", description: "Explicit App DID", type: "string" }
5339
+ { name: "app-did", property: "app_did", description: "Explicit App DID", type: "string" },
5340
+ {
5341
+ name: "composite",
5342
+ description: "aiws-controls: start the package with a composite control (a panel of Blocks) instead of a declarative one",
5343
+ type: "boolean"
5344
+ }
5301
5345
  ],
5302
5346
  inputSchema: {
5303
5347
  type: "object",
@@ -5308,7 +5352,8 @@ function createPikgModule(dependencies = {}) {
5308
5352
  kind: { type: "string", enum: APP_KINDS },
5309
5353
  source: { type: "string", minLength: 1 },
5310
5354
  version: { type: "string", minLength: 1 },
5311
- app_did: { type: "string", minLength: 1 }
5355
+ app_did: { type: "string", minLength: 1 },
5356
+ composite: { type: "boolean" }
5312
5357
  },
5313
5358
  additionalProperties: false
5314
5359
  },
@@ -5322,7 +5367,9 @@ function createPikgModule(dependencies = {}) {
5322
5367
  examples: [
5323
5368
  "buckyos pikg init .",
5324
5369
  "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"
5370
+ "buckyos --non-interactive pikg init . --owner did:bns:root --kind agent-skills --source ./skills",
5371
+ "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"
5326
5373
  ],
5327
5374
  handler: async (ctx, input) => await initCommand(ctx, input, docker)
5328
5375
  },
@@ -5345,7 +5392,7 @@ function createPikgModule(dependencies = {}) {
5345
5392
  execution: "local",
5346
5393
  networkAccess: false,
5347
5394
  examples: ["buckyos pikg build ./dapp_meta"],
5348
- handler: async (ctx, input) => await buildCommand(ctx, input, docker, now)
5395
+ handler: async (ctx, input) => await buildCommand$1(ctx, input, docker, now)
5349
5396
  },
5350
5397
  {
5351
5398
  verb: "pack",
@@ -5562,11 +5609,15 @@ async function initCommand(ctx, input, docker) {
5562
5609
  try {
5563
5610
  await getHost().lstat(absoluteSource);
5564
5611
  } catch (error) {
5565
- if (isHostError(error, "NotFound")) {
5612
+ if (!isHostError(error, "NotFound"))
5613
+ throw error;
5614
+ if (kind === "aiws-controls") {
5615
+ await writeAiwsSkeleton(absoluteSource, owner, name, input.composite === true);
5616
+ sourceSummary2.skeleton = true;
5617
+ } else {
5566
5618
  await ctx.io.stderr(`warning: source does not exist yet: ${sourceSummary2.path}
5567
5619
  `);
5568
- } else
5569
- throw error;
5620
+ }
5570
5621
  }
5571
5622
  }
5572
5623
  const appMeta = {
@@ -5626,7 +5677,26 @@ async function initCommand(ctx, input, docker) {
5626
5677
  next_command: `buckyos pikg build ${metaDir}`
5627
5678
  };
5628
5679
  }
5629
- async function buildCommand(ctx, input, docker, now) {
5680
+ async function writeAiwsSkeleton(dir, ownerDid, name, composite) {
5681
+ const kind = composite ? "composite" : "declarative";
5682
+ const files = controlSkeleton({ authorDid: ownerDid, name, title: name, kind });
5683
+ const encoder = new TextEncoder();
5684
+ const host2 = getHost();
5685
+ await host2.mkdir(join(dir, "controls", name), { recursive: true });
5686
+ for (const [file, text2] of Object.entries(files))
5687
+ await host2.writeTextFile(join(dir, "controls", name, file), text2);
5688
+ const digest = await aiwsContentDigest(Object.entries(files).map(([file, text2]) => [file, encoder.encode(text2)]));
5689
+ const manifest = {
5690
+ manifest_version: AIWS_MANIFEST_VERSION,
5691
+ purpose: "aiws",
5692
+ host_api: composite ? {} : { declarative: 1 },
5693
+ controls: [{ ident: `${ownerDid}#${name}`, name, kind, version: 1, digest, dir: `controls/${name}/`, title: name }],
5694
+ workspace_templates: []
5695
+ };
5696
+ await host2.writeTextFile(join(dir, AIWS_MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
5697
+ `);
5698
+ }
5699
+ async function buildCommand$1(ctx, input, docker, now) {
5630
5700
  const metaInput = optionalInputString(input, "meta_dir") ?? "./dapp_meta";
5631
5701
  const metaDir = await realDirectory(resolveFromCwd$1(ctx, metaInput), "dapp_meta");
5632
5702
  const appMeta = parseAppMeta(await readJson(join(metaDir, "app.json"), "app.json"));
@@ -11098,7 +11168,12 @@ async function aiws(ctx, method, params) {
11098
11168
  const error = expectObject$4(result.error, `${method}.error`);
11099
11169
  const code = typeof error.code === "string" ? error.code : "AIWS_ERROR";
11100
11170
  const detail = typeof error.detail === "string" ? error.detail : code;
11101
- throw new ToolError(code, `${code}${typeof error.sub_code === "string" ? `/${error.sub_code}` : ""}: ${detail}`);
11171
+ const details = {};
11172
+ if (typeof error.sub_code === "string")
11173
+ details.sub_code = error.sub_code;
11174
+ if (error.data !== void 0)
11175
+ details.data = error.data;
11176
+ throw new ToolError(code, `${code}${typeof error.sub_code === "string" ? `/${error.sub_code}` : ""}: ${detail}`, void 0, false, details);
11102
11177
  }
11103
11178
  return result;
11104
11179
  }
@@ -11471,11 +11546,630 @@ const exportCommand = {
11471
11546
  return { ok: true, format: result.format, kind: result.kind, path: out };
11472
11547
  }
11473
11548
  };
11549
+ async function sessionToken(ctx) {
11550
+ const session = ctx.session;
11551
+ if (!session)
11552
+ throw new ToolError("AUTH_REQUIRED", "authenticated session is required", EXIT_AUTH);
11553
+ return (await session.ensureValid()).token;
11554
+ }
11555
+ async function openHome(ctx) {
11556
+ const session = ctx.session;
11557
+ if (!session)
11558
+ throw new ToolError("AUTH_REQUIRED", "authenticated session is required", EXIT_AUTH);
11559
+ const nfsp = new NfspClient({ baseUrl: gatewayOrigin$2(ctx.connection.endpoint), sessionToken: async () => (await session.ensureValid()).token });
11560
+ await nfsp.hello(["cli"]);
11561
+ return nfsp;
11562
+ }
11563
+ function extRpc(ctx) {
11564
+ return {
11565
+ extBuild: (workspace_id, params) => aiws(ctx, "ext.build", { workspace_id, ...params }),
11566
+ extExport: (workspace_id, entity_ids, purpose = "package") => aiws(ctx, "ext.export", { workspace_id, entity_ids, purpose })
11567
+ };
11568
+ }
11569
+ function assetFetcher(ctx) {
11570
+ return async (workspaceId, objId) => {
11571
+ const response = await fetch(`${gatewayOrigin$2(ctx.connection.endpoint)}/kapi/aiworkspace/asset/${encodeURIComponent(workspaceId)}/${encodeURIComponent(objId)}`, {
11572
+ headers: { Authorization: `Bearer ${await sessionToken(ctx)}` },
11573
+ signal: ctx.signal
11574
+ });
11575
+ if (!response.ok)
11576
+ throw new ToolError("AIWS_ASSET_UNAVAILABLE", `asset ${objId}: HTTP ${response.status}`);
11577
+ return new Uint8Array(await response.arrayBuffer());
11578
+ };
11579
+ }
11580
+ async function sourceErrors(run) {
11581
+ try {
11582
+ return await run();
11583
+ } catch (error) {
11584
+ if (error instanceof AiwsSourceError)
11585
+ throw new ToolError("BUILD_FAILED", `BUILD_FAILED: ${error.message}`, void 0, false, { data: { diagnostics: error.diagnostics } });
11586
+ throw error;
11587
+ }
11588
+ }
11589
+ function list(input, key) {
11590
+ return requiredInputString(input, key).split(",").map((s) => s.trim()).filter(Boolean);
11591
+ }
11592
+ const IDENT = /^did:[a-z0-9]+:[^/#\s]+#[a-z0-9][a-z0-9._-]{0,63}$/;
11593
+ async function migrationFile(ctx, path) {
11594
+ const host2 = getHost();
11595
+ let value;
11596
+ try {
11597
+ value = JSON.parse(await host2.readTextFile(host2.path.resolve(ctx.cwd, path)));
11598
+ } catch (error) {
11599
+ throw new UsageError("AIWS_USAGE", `--migration ${path}: ${error instanceof Error ? error.message : String(error)}`);
11600
+ }
11601
+ const shape = `--migration ${path}: { "<author_did>#<name>": { config?, bindings?, slots?, events? } }`;
11602
+ if (!value || typeof value !== "object" || Array.isArray(value))
11603
+ throw new UsageError("AIWS_USAGE", shape);
11604
+ for (const [ident, m] of Object.entries(value)) {
11605
+ if (!IDENT.test(ident) || !m || typeof m !== "object" || Array.isArray(m) || Object.keys(m).some((k) => !["config", "bindings", "slots", "events"].includes(k))) {
11606
+ throw new UsageError("AIWS_USAGE", `${shape} (at ${ident})`);
11607
+ }
11608
+ }
11609
+ return value;
11610
+ }
11611
+ const LOCAL_STATE = ".aiws-build.json";
11612
+ async function isLocalDir(path) {
11613
+ try {
11614
+ return (await getHost().stat(path)).isDirectory;
11615
+ } catch {
11616
+ return false;
11617
+ }
11618
+ }
11619
+ async function localFiles(dir) {
11620
+ const host2 = getHost();
11621
+ const files = [];
11622
+ const problems = [];
11623
+ const walk = async (folder, prefix, depth) => {
11624
+ for (const entry of await host2.readDir(folder)) {
11625
+ if (entry.name.startsWith("."))
11626
+ continue;
11627
+ const name = `${prefix}${entry.name}`;
11628
+ if (entry.isDirectory) {
11629
+ if (depth >= 8)
11630
+ problems.push({ file: name, severity: "error", message: "sub-folders nest at most 8 levels" });
11631
+ else
11632
+ await walk(host2.path.join(folder, entry.name), `${name}/`, depth + 1);
11633
+ } else if (entry.isFile)
11634
+ files.push({ name, bytes: await host2.readFile(host2.path.join(folder, entry.name)) });
11635
+ else
11636
+ problems.push({ file: name, severity: "error", message: "only files and folders belong to a control source" });
11637
+ }
11638
+ };
11639
+ await walk(dir, "", 0);
11640
+ if (files.length === 0)
11641
+ problems.push({ file: "", severity: "error", message: `${dir} holds no files` });
11642
+ if (problems.length > 0)
11643
+ throw new ToolError("BUILD_FAILED", `BUILD_FAILED: ${problems.map((p) => `${p.file}: ${p.message}`).join("; ")}`, void 0, false, { data: { diagnostics: problems } });
11644
+ return files.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
11645
+ }
11646
+ function sourceUploader(ctx) {
11647
+ return async (name, bytes) => {
11648
+ const bucket = `user/aiworkspace/${ctx.principal.id}/exports`;
11649
+ const key = `aiws-src/${Date.now()}-${name.replaceAll("/", "_")}`;
11650
+ const url = `${gatewayOrigin$2(ctx.connection.endpoint)}/bucket/v1/${bucket}/${key.split("/").map(encodeURIComponent).join("/")}`;
11651
+ const response = await fetch(url, { method: "PUT", headers: { Authorization: `Bearer ${await sessionToken(ctx)}` }, body: new Blob([bytes]), signal: ctx.signal });
11652
+ if (!response.ok)
11653
+ throw new ToolError("AIWS_UPLOAD_FAILED", `upload of ${name}: HTTP ${response.status} ${await response.text().catch(() => "")}`);
11654
+ const put = await response.json();
11655
+ if (!put.obj_id)
11656
+ throw new ToolError("AIWS_UPLOAD_FAILED", `upload of ${name}: the bucket answered no obj_id`);
11657
+ return put.obj_id;
11658
+ };
11659
+ }
11660
+ async function localState(dir) {
11661
+ try {
11662
+ return JSON.parse(await getHost().readTextFile(getHost().path.join(dir, LOCAL_STATE)));
11663
+ } catch {
11664
+ return null;
11665
+ }
11666
+ }
11667
+ async function buildLocal(ctx, ws, dir, opts) {
11668
+ const state = await localState(dir);
11669
+ const result = await aiws(ctx, "ext.build", {
11670
+ workspace_id: ws,
11671
+ files: await sourceErrors(async () => packBuildFiles(await localFiles(dir), sourceUploader(ctx))),
11672
+ ...opts.folderId ? { folder_id: opts.folderId } : {},
11673
+ ...state && state.workspace_id === ws ? { expect: { def_rev: state.def_rev, dir_rev: state.dir_rev } } : {},
11674
+ ...opts.force ? { force: true } : {},
11675
+ ...opts.dryRun ? { dry_run: true } : {},
11676
+ ...opts.migration ? { migration: opts.migration } : {}
11677
+ });
11678
+ if (!opts.dryRun && typeof result.def_entity_id === "string" && typeof result.def_rev === "number") {
11679
+ 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)}
11680
+ `);
11681
+ }
11682
+ return result;
11683
+ }
11684
+ const buildCommand = {
11685
+ verb: "build",
11686
+ 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",
11687
+ description: 'The folder is a local folder (when it exists here; its files are sent inline, and past 4 MiB per build the largest are uploaded to your exports bucket and sent by reference) or a Zone path (e.g. /home/alice/AI Workspace/Demo/controls/kpi-card; --zone-path forces it); sub-folders are part of the source (file names are relative paths). 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. A build that narrows what instances rely on (a binding renamed, a slot moved, an event dropped) takes --migration, a JSON file { "<author_did>#<name>": Migration } (as ext-upgrade).',
11688
+ positionals: [{ name: "dir", description: "Local folder, or Zone path of the control source folder", required: true }],
11689
+ options: [
11690
+ WORKSPACE_OPTION,
11691
+ { name: "folder", property: "folder_id", description: "Data folder for the definition (default: data/extensions)", type: "string" },
11692
+ { name: "watch", property: "watch", description: "Rebuild whenever the folder changes", type: "boolean" },
11693
+ { name: "dry-run", property: "dry_run", description: "Check only: diagnostics, nothing committed", type: "boolean" },
11694
+ { name: "force", property: "force", description: "Build over whatever the Workspace holds now", type: "boolean" },
11695
+ { name: "zone-path", property: "zone_path", description: "Treat the folder as a Zone path even if a local folder has that name", type: "boolean" },
11696
+ { name: "migration", property: "migration", description: 'JSON file { "<author_did>#<name>": Migration } rewriting the instances of a build that narrows the contract', type: "string" }
11697
+ ],
11698
+ 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" }, migration: { type: "string" } }, ["dir"]),
11699
+ outputSchema: OBJECT_OUTPUT,
11700
+ resultSchemaVersion: 1,
11701
+ access: { mode: "fixed", level: "write" },
11702
+ asyncMode: "sync",
11703
+ requiresSession: true,
11704
+ 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'],
11705
+ handler: async (ctx, input) => {
11706
+ const ws = requiredInputString(input, "workspace_id");
11707
+ const dir = requiredInputString(input, "dir");
11708
+ const migration = inputString(input, "migration");
11709
+ const opts = { folderId: inputString(input, "folder_id"), force: input.force === true, dryRun: input.dry_run === true, upload: sourceUploader(ctx), ...migration ? { migration: await migrationFile(ctx, migration) } : {} };
11710
+ if (input.zone_path !== true && await isLocalDir(getHost().path.resolve(ctx.cwd, dir))) {
11711
+ const local = getHost().path.resolve(ctx.cwd, dir);
11712
+ if (input.watch === true)
11713
+ throw new UsageError("AIWS_USAGE", "--watch follows a Zone folder; rebuild a local folder yourself");
11714
+ return buildLocal(ctx, ws, local, opts);
11715
+ }
11716
+ const nfsp = await openHome(ctx);
11717
+ if (input.watch !== true)
11718
+ return sourceErrors(() => buildControl(extRpc(ctx), nfsp, ws, dir, opts));
11719
+ let builds = 0;
11720
+ let failures = 0;
11721
+ await watchControl(nfsp, dir, async () => {
11722
+ try {
11723
+ const r = await sourceErrors(() => buildControl(extRpc(ctx), nfsp, ws, dir, opts));
11724
+ builds++;
11725
+ await ctx.io.stderr(`built ${r.ident} v${r.version ?? "?"} (${r.unchanged ? "unchanged" : r.status})
11726
+ `);
11727
+ } catch (error) {
11728
+ failures++;
11729
+ const diagnostics = error instanceof ToolError ? error.details.data?.diagnostics : void 0;
11730
+ await ctx.io.stderr(`build failed: ${error instanceof Error ? error.message : String(error)}
11731
+ ${diagnostics ? `${JSON.stringify(diagnostics, null, 2)}
11732
+ ` : ""}`);
11733
+ }
11734
+ }, { signal: ctx.signal });
11735
+ return { ok: true, watched: dir, builds, failures };
11736
+ }
11737
+ };
11738
+ const pullCommand = {
11739
+ verb: "pull",
11740
+ 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",
11741
+ positionals: [{ name: "def", description: "Definition (buckyos.block-def) id", required: true }],
11742
+ 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" }],
11743
+ inputSchema: workspaceInput({ def: { type: "string", minLength: 1 }, out: { type: "string", minLength: 1 }, local: { type: "boolean" } }, ["def", "out"]),
11744
+ outputSchema: OBJECT_OUTPUT,
11745
+ resultSchemaVersion: 1,
11746
+ access: { mode: "fixed", level: "write" },
11747
+ asyncMode: "sync",
11748
+ requiresSession: true,
11749
+ examples: ['buckyos aiws pull def_abc --workspace ws_abc --out "/home/alice/AI Workspace/Demo/controls/kpi-card"'],
11750
+ handler: async (ctx, input) => {
11751
+ if (input.local === true) {
11752
+ const ws = requiredInputString(input, "workspace_id");
11753
+ const host2 = getHost();
11754
+ const out = host2.path.resolve(ctx.cwd, requiredInputString(input, "out"));
11755
+ const r2 = await aiws(ctx, "ext.export", { workspace_id: ws, entity_ids: [requiredInputString(input, "def")], purpose: "pull" });
11756
+ const control = expectObject$4(r2.controls[0], "ext.export.controls[0]");
11757
+ await host2.mkdir(out, { recursive: true });
11758
+ const fetchAsset = assetFetcher(ctx);
11759
+ const files = [];
11760
+ for (const f of control.files) {
11761
+ const target = host2.path.join(out, ...f.name.split("/"));
11762
+ await host2.mkdir(host2.path.dirname(target), { recursive: true });
11763
+ if (f.text !== void 0)
11764
+ await host2.writeTextFile(target, f.text);
11765
+ else
11766
+ await host2.writeFile(target, await fetchAsset(ws, String(f.obj_id)));
11767
+ files.push(f.name);
11768
+ }
11769
+ 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)}
11770
+ `);
11771
+ return { ok: true, out, files };
11772
+ }
11773
+ const nfsp = await openHome(ctx);
11774
+ const r = await pullControl(extRpc(ctx), nfsp, requiredInputString(input, "workspace_id"), requiredInputString(input, "def"), requiredInputString(input, "out"), assetFetcher(ctx));
11775
+ return { ok: true, out: requiredInputString(input, "out"), ...r };
11776
+ }
11777
+ };
11778
+ async function pikgVerb(ctx, verb, input) {
11779
+ const command = createPikgModule().commands.find((c) => c.verb === verb);
11780
+ if (!command)
11781
+ throw new ToolError("INTERNAL", `pikg ${verb} is missing`);
11782
+ return expectObject$4(await command.handler(ctx, input), `pikg ${verb}`);
11783
+ }
11784
+ async function uploadPackage(ctx, path) {
11785
+ const bucket_id = `user/aiworkspace/${ctx.principal.id}/exports`;
11786
+ const key = `aiws/${Date.now()}-${getHost().path.basename(path)}`;
11787
+ const bytes = await getHost().readFile(path);
11788
+ if (bytes.byteLength > 32 * 1024 * 1024)
11789
+ throw new UsageError("FILE_TOO_LARGE", "a control package installed from the CLI is limited to 32 MiB");
11790
+ const url = `${gatewayOrigin$2(ctx.connection.endpoint)}/bucket/v1/${bucket_id}/${key.split("/").map(encodeURIComponent).join("/")}`;
11791
+ const response = await fetch(url, { method: "PUT", headers: { Authorization: `Bearer ${await sessionToken(ctx)}` }, body: new Blob([bytes]), signal: ctx.signal });
11792
+ if (!response.ok)
11793
+ throw new ToolError("AIWS_UPLOAD_FAILED", `upload of ${path}: HTTP ${response.status} ${await response.text().catch(() => "")}`);
11794
+ return { bucket_id, key };
11795
+ }
11796
+ const PACKAGE_INPUT = { name: { type: "string" }, version: { type: "string" }, owner: { type: "string" }, pikg: { type: "boolean" }, install: { type: "boolean" } };
11797
+ function packageOptions(nameDefault) {
11798
+ return [
11799
+ { name: "name", property: "name", description: `Package name (default: ${nameDefault})`, type: "string" },
11800
+ { name: "version", property: "version", description: "Package version (default 0.1.0)", type: "string" },
11801
+ { name: "owner", property: "owner", description: "Owner DID of the package (default did:bns:<you>)", type: "string" },
11802
+ { name: "pikg", property: "pikg", description: "Also pack the bundle as an aiws PIKG", type: "boolean" },
11803
+ { name: "install", property: "install", description: "Pack, then install into your AI Workspace library (dev mode)", type: "boolean" }
11804
+ ];
11805
+ }
11806
+ const fromBase64 = (text2) => Uint8Array.from(atob(text2), (c) => c.charCodeAt(0));
11807
+ async function writeBundle(out, files) {
11808
+ const host2 = getHost();
11809
+ const root = host2.path.join(out, "aiws");
11810
+ await host2.remove(root, { recursive: true }).catch(() => void 0);
11811
+ for (const [path, bytes] of files) {
11812
+ const target = host2.path.join(root, path);
11813
+ await host2.mkdir(host2.path.dirname(target), { recursive: true });
11814
+ await host2.writeFile(target, bytes);
11815
+ }
11816
+ return root;
11817
+ }
11818
+ async function packBundle(ctx, input, out, defaultName, result) {
11819
+ if (input.pikg !== true && input.install !== true)
11820
+ return result;
11821
+ const host2 = getHost();
11822
+ const name = inputString(input, "name") ?? defaultName;
11823
+ const owner = inputString(input, "owner") ?? `did:bns:${ctx.principal.id}`;
11824
+ const version = inputString(input, "version") ?? "0.1.0";
11825
+ const appJson = host2.path.join(out, "dapp_meta", "app.json");
11826
+ const existing = await host2.readTextFile(appJson).then((t) => JSON.parse(t)).catch(() => null);
11827
+ if (!existing) {
11828
+ await pikgVerb(ctx, "init", { project_dir: out, name, owner, kind: "aiws-controls", source: "./aiws", version });
11829
+ } else if (inputString(input, "version") && existing.version !== version) {
11830
+ await host2.writeTextFile(appJson, `${JSON.stringify({ ...existing, version }, null, 2)}
11831
+ `);
11832
+ const pikgJson = host2.path.join(out, "dapp_meta", "pikg.json");
11833
+ const meta = JSON.parse(await host2.readTextFile(pikgJson));
11834
+ await host2.writeTextFile(pikgJson, `${JSON.stringify({ ...meta, pikg_file: `${existing.name}-${version}.pikg` }, null, 2)}
11835
+ `);
11836
+ }
11837
+ await pikgVerb(ctx, "build", { meta_dir: host2.path.join(out, "dapp_meta") });
11838
+ const packed = await pikgVerb(ctx, "pack", { dist_dir: host2.path.join(out, "dapp_dist") });
11839
+ const pikgPath = String(packed.pikg_path);
11840
+ result.pikg = pikgPath;
11841
+ if (input.install !== true)
11842
+ return result;
11843
+ result.install = await aiws(ctx, "ext.install", { pikg: await uploadPackage(ctx, pikgPath), mode: "dev" });
11844
+ return result;
11845
+ }
11846
+ const controlSummary = (controls) => controls.map((c) => ({ ident: c.ident, kind: c.kind, version: c.version, digest: c.digest }));
11847
+ const extExportCommand = {
11848
+ verb: "ext-export",
11849
+ summary: "Export Workspace controls as a bundle in a local folder; optionally pack it as an aiws PIKG and install it into your library",
11850
+ 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.",
11851
+ positionals: [{ name: "defs", description: "Definition (or instance) ids, comma-separated", required: true }],
11852
+ options: [
11853
+ WORKSPACE_OPTION,
11854
+ { name: "out", property: "out", description: "Local folder to write into (created)", type: "string", required: true },
11855
+ ...packageOptions("the first control’s")
11856
+ ],
11857
+ inputSchema: workspaceInput({ defs: { type: "string", minLength: 1 }, out: { type: "string", minLength: 1 }, ...PACKAGE_INPUT }, ["defs", "out"]),
11858
+ outputSchema: OBJECT_OUTPUT,
11859
+ resultSchemaVersion: 1,
11860
+ access: { mode: "fixed", level: "write" },
11861
+ asyncMode: "sync",
11862
+ requiresSession: true,
11863
+ examples: ["buckyos aiws ext-export def_abc,def_xyz --workspace ws_abc --out ./kpi-pack --install"],
11864
+ handler: async (ctx, input) => {
11865
+ const out = getHost().path.resolve(ctx.cwd, requiredInputString(input, "out"));
11866
+ const bundle = await exportBundle(extRpc(ctx), requiredInputString(input, "workspace_id"), list(input, "defs"), assetFetcher(ctx));
11867
+ const result = { ok: true, bundle: await writeBundle(out, bundle.files), controls: controlSummary(bundle.manifest.controls) };
11868
+ return packBundle(ctx, input, out, bundle.manifest.controls[0]?.name.replace(/[._]/g, "-") ?? "controls", result);
11869
+ }
11870
+ };
11871
+ const extUpgradeCommand = {
11872
+ verb: "ext-upgrade",
11873
+ summary: "Upgrade controls a Workspace imported from your library to the version your library holds now (one Commit; instances keep their overrides and content)",
11874
+ description: 'Each definition is rebuilt from its package in your library (--pkg, or the package it was imported from), with what it builds on from the same package, dependencies first. A definition built locally since its import is refused (EXT_LOCAL_CHANGES; the error data lists the changed files) unless --force. A version that narrows what instances rely on needs --migration, a JSON file { "<author_did>#<name>": Migration } keyed by identity (config / bindings { rename, drop, fill }, slots { rename, move }, events { rename, drop }); --dry-run answers what it would write on each instance. Across Zones: `aiws library bundle <pkg_id> --out <file>` in the Zone of the library, then `aiws ext-import --bundle <file> --upgrade` in the Zone of the Workspace.',
11875
+ positionals: [{ name: "defs", description: "Definition (buckyos.block-def) ids, comma-separated", required: true }],
11876
+ options: [
11877
+ WORKSPACE_OPTION,
11878
+ { name: "pkg", property: "pkg_id", description: "Library package to upgrade from (default: the one each definition was imported from)", type: "string" },
11879
+ { name: "migration", property: "migration", description: 'JSON file { "<author_did>#<name>": Migration } rewriting the instances of a narrowing version', type: "string" },
11880
+ { name: "dry-run", property: "dry_run", description: "Check only: the plan per instance, nothing committed", type: "boolean" },
11881
+ { name: "force", property: "force", description: "Upgrade over local changes (they are replaced)", type: "boolean" }
11882
+ ],
11883
+ inputSchema: workspaceInput({ defs: { type: "string", minLength: 1 }, pkg_id: { type: "string" }, migration: { type: "string" }, dry_run: { type: "boolean" }, force: { type: "boolean" } }, ["defs"]),
11884
+ outputSchema: OBJECT_OUTPUT,
11885
+ resultSchemaVersion: 1,
11886
+ access: { mode: "fixed", level: "write" },
11887
+ asyncMode: "sync",
11888
+ requiresSession: true,
11889
+ examples: ["buckyos aiws ext-upgrade def_abc --workspace ws_abc --dry-run", "buckyos aiws ext-upgrade def_abc,def_xyz --workspace ws_abc --migration ./migration.json"],
11890
+ handler: async (ctx, input) => {
11891
+ const pkg = inputString(input, "pkg_id");
11892
+ const migration = inputString(input, "migration");
11893
+ return aiws(ctx, "ext.upgrade", {
11894
+ workspace_id: requiredInputString(input, "workspace_id"),
11895
+ items: list(input, "defs").map((def_entity_id) => ({ def_entity_id, ...pkg ? { pkg_id: pkg } : {} })),
11896
+ ...migration ? { migration: await migrationFile(ctx, migration) } : {},
11897
+ ...input.force === true ? { force: true } : {},
11898
+ ...input.dry_run === true ? { dry_run: true } : {},
11899
+ ...ctx.idempotencyKey ? { idempotency_key: ctx.idempotencyKey } : {}
11900
+ });
11901
+ }
11902
+ };
11903
+ const libraryCommand = {
11904
+ verb: "library",
11905
+ summary: "Your AI Workspace control library: `list` (each control with the identities it depends on, `deps`, and the package’s templates), `install <file.pikg>` (an aiws package, dev mode), `remove <pkg_id>`, `bundle <pkg_id> --out <file.json>` (the controls with their bytes, for `ext-import --bundle` in another Zone)",
11906
+ positionals: [{ name: "action", description: "list | install | remove | bundle", required: true }, { name: "target", description: "install: local .pikg path; remove, bundle: package id", required: false }],
11907
+ options: [
11908
+ { name: "out", property: "out", description: "bundle: the JSON file to write", type: "string" },
11909
+ { name: "idents", property: "idents", description: "bundle: only these controls (and what they build on), comma-separated identities", type: "string" }
11910
+ ],
11911
+ inputSchema: { type: "object", properties: { action: { type: "string", enum: ["list", "install", "remove", "bundle"] }, target: { type: "string" }, out: { type: "string" }, idents: { type: "string" } }, required: ["action"], additionalProperties: false },
11912
+ outputSchema: OBJECT_OUTPUT,
11913
+ resultSchemaVersion: 1,
11914
+ access: { mode: "fixed", level: "write" },
11915
+ asyncMode: "sync",
11916
+ requiresSession: true,
11917
+ examples: ["buckyos aiws library list", "buckyos aiws library install ./kpi-pack-0.1.0.pikg", "buckyos aiws library remove kpi-pack.alice.bns.did", "buckyos aiws library bundle kpi-pack.alice.bns.did --out ./kpi-pack.bundle.json"],
11918
+ handler: async (ctx, input) => {
11919
+ const action = requiredInputString(input, "action");
11920
+ if (action === "list")
11921
+ return aiws(ctx, "ext.list_library", {});
11922
+ if (action === "remove")
11923
+ return aiws(ctx, "ext.uninstall", { pkg_id: requiredInputString(input, "target") });
11924
+ if (action === "bundle") {
11925
+ const host2 = getHost();
11926
+ const out = host2.path.resolve(ctx.cwd, requiredInputString(input, "out"));
11927
+ const idents = inputString(input, "idents");
11928
+ const r = await aiws(ctx, "ext.library_bundle", { pkg_id: requiredInputString(input, "target"), ...idents ? { idents: list(input, "idents") } : {} });
11929
+ const controls = Array.isArray(r.controls) ? r.controls : [];
11930
+ await host2.writeTextFile(out, `${JSON.stringify({ pkg_id: r.pkg_id, controls }, null, 2)}
11931
+ `);
11932
+ return { ok: true, out, pkg_id: r.pkg_id, controls: controls.map((c) => ({ ident: c.ident, version: c.version, digest: c.digest, deps: c.deps })) };
11933
+ }
11934
+ if (action !== "install")
11935
+ throw new UsageError("AIWS_USAGE", "library: list, install <file.pikg>, remove <pkg_id> or bundle <pkg_id> --out <file.json>");
11936
+ const path = getHost().path.resolve(ctx.cwd, requiredInputString(input, "target"));
11937
+ return aiws(ctx, "ext.install", { pikg: await uploadPackage(ctx, path), mode: "dev" });
11938
+ }
11939
+ };
11940
+ async function relayBundle(ctx, ws, controls, opts) {
11941
+ const pending = [...controls];
11942
+ const needed = new Set(controls.flatMap((c) => c.deps ?? []));
11943
+ const out = [];
11944
+ while (pending.length > 0) {
11945
+ const left = new Set(pending.map((c2) => c2.ident));
11946
+ const c = pending.splice(Math.max(0, pending.findIndex((p) => (p.deps ?? []).every((d) => !left.has(d) || d === p.ident))), 1)[0];
11947
+ try {
11948
+ const r = await aiws(ctx, "ext.build", {
11949
+ workspace_id: ws,
11950
+ files: await sourceErrors(() => packBuildFiles(c.files.map((f) => ({ name: f.name, bytes: fromBase64(f.base64) })), sourceUploader(ctx))),
11951
+ provenance: c.provenance,
11952
+ ...opts.folderId ? { folder_id: opts.folderId } : {},
11953
+ ...opts.upgrade ? { upgrade: true } : {},
11954
+ ...opts.migration?.[c.ident] ? { migration: { [c.ident]: opts.migration[c.ident] } } : {}
11955
+ });
11956
+ out.push({ ident: c.ident, def_entity_id: r.def_entity_id, action: r.action ?? r.status, version: r.version });
11957
+ } catch (error) {
11958
+ if (opts.upgrade || !needed.has(c.ident) || !(error instanceof ToolError) || error.details.sub_code !== "EXT_IDENT_EXISTS")
11959
+ throw error;
11960
+ out.push({ ident: c.ident, def_entity_id: error.details.data?.def_entity_id, action: "satisfied" });
11961
+ }
11962
+ }
11963
+ return out;
11964
+ }
11965
+ const extImportCommand = {
11966
+ verb: "ext-import",
11967
+ summary: "Bring controls of your library into a Workspace (one Commit): <pkg_id>#<ident>, comma-separated; what they build on (deps) comes along, dependencies first, and an identity the Workspace already has satisfies it. --bundle builds a library bundle of another Zone instead",
11968
+ description: 'Across Zones: `aiws library bundle <pkg_id> --out <file>` in the Zone of the library, then `aiws ext-import --bundle <file> --workspace <ws>` in the Zone of the Workspace: each control is built with its package provenance, dependencies first; a dependency the Workspace already holds is met by that definition. --upgrade (with --bundle) upgrades the identities the Workspace holds from another version, --migration naming a JSON file { "<author_did>#<name>": Migration } for a version that narrows what instances rely on. Upgrades from your library in the same Zone are `aiws ext-upgrade`.',
11969
+ positionals: [{ name: "items", description: "<pkg_id>#<author_did>#<name>[,…] (ext.list_library names them); not with --bundle", required: false }],
11970
+ options: [
11971
+ WORKSPACE_OPTION,
11972
+ { name: "folder", property: "folder_id", description: "Data folder for the definitions (default: data/extensions)", type: "string" },
11973
+ { name: "bundle", property: "bundle", description: "A library bundle file (`aiws library bundle`, any Zone) to build instead of items", type: "string" },
11974
+ { name: "upgrade", property: "upgrade", description: "With --bundle: upgrade identities the Workspace holds from another version", type: "boolean" },
11975
+ { name: "migration", property: "migration", description: 'With --upgrade: JSON file { "<author_did>#<name>": Migration }', type: "string" }
11976
+ ],
11977
+ inputSchema: workspaceInput({ items: { type: "string", minLength: 1 }, folder_id: { type: "string" }, bundle: { type: "string" }, upgrade: { type: "boolean" }, migration: { type: "string" } }),
11978
+ outputSchema: OBJECT_OUTPUT,
11979
+ resultSchemaVersion: 1,
11980
+ access: { mode: "fixed", level: "write" },
11981
+ asyncMode: "sync",
11982
+ requiresSession: true,
11983
+ examples: ["buckyos aiws ext-import kpi-pack.alice.bns.did#did:bns:alice#kpi-card --workspace ws_abc", "buckyos aiws ext-import --bundle ./kpi-pack.bundle.json --workspace ws_abc --upgrade"],
11984
+ handler: async (ctx, input) => {
11985
+ const ws = requiredInputString(input, "workspace_id");
11986
+ const folder = inputString(input, "folder_id");
11987
+ const bundle = inputString(input, "bundle");
11988
+ const migration = inputString(input, "migration");
11989
+ if (migration && input.upgrade !== true)
11990
+ throw new UsageError("AIWS_USAGE", "--migration goes with --upgrade");
11991
+ if (!bundle) {
11992
+ if (input.upgrade === true)
11993
+ throw new UsageError("AIWS_USAGE", "--upgrade goes with --bundle (a library bundle of another Zone); upgrade what came from your own library with `aiws ext-upgrade`");
11994
+ const items = list(input, "items").map((item) => {
11995
+ const cut = item.indexOf("#");
11996
+ if (cut <= 0)
11997
+ throw new UsageError("AIWS_USAGE", `${item}: <pkg_id>#<ident>`);
11998
+ return { pkg_id: item.slice(0, cut), ident: item.slice(cut + 1) };
11999
+ });
12000
+ return aiws(ctx, "ext.import", { workspace_id: ws, items, ...folder ? { folder_id: folder } : {} });
12001
+ }
12002
+ if (inputString(input, "items"))
12003
+ throw new UsageError("AIWS_USAGE", "pass items or --bundle, not both");
12004
+ const host2 = getHost();
12005
+ const file = JSON.parse(await host2.readTextFile(host2.path.resolve(ctx.cwd, bundle)));
12006
+ if (!Array.isArray(file.controls) || file.controls.some((c) => !c || typeof c.ident !== "string" || !Array.isArray(c.files))) {
12007
+ throw new UsageError("AIWS_USAGE", `--bundle ${bundle}: not a library bundle ({ pkg_id, controls: [{ ident, provenance, files }] }, from \`aiws library bundle\`)`);
12008
+ }
12009
+ const controls = await relayBundle(ctx, ws, file.controls, { folderId: folder, upgrade: input.upgrade === true, ...migration ? { migration: await migrationFile(ctx, migration) } : {} });
12010
+ return { ok: true, controls };
12011
+ }
12012
+ };
12013
+ function createParams(text2, declared) {
12014
+ const out = {};
12015
+ for (const pair of text2.split(",").map((s) => s.trim()).filter(Boolean)) {
12016
+ const cut = pair.indexOf("=");
12017
+ const name = cut > 0 ? pair.slice(0, cut) : "";
12018
+ if (!Object.hasOwn(declared, name))
12019
+ throw new UsageError("AIWS_USAGE", `--param ${pair}: the template's params are ${Object.keys(declared).join(", ") || "(none)"}`);
12020
+ out[name] = typedValue(declared[name].type, pair.slice(cut + 1), `--param ${pair}`);
12021
+ }
12022
+ return out;
12023
+ }
12024
+ function exportParams(text2) {
12025
+ const out = {};
12026
+ for (const item of text2.split(",").map((s) => s.trim()).filter(Boolean)) {
12027
+ const [name, type, ...rest] = item.split(":");
12028
+ if (!name || !["string", "number", "boolean"].includes(type))
12029
+ throw new UsageError("AIWS_USAGE", `--param ${item}: <name>:<string | number | boolean>[:<default>]`);
12030
+ out[name] = { type, ...rest.length > 0 ? { default: typedValue(type, rest.join(":"), `--param ${item}`) } : {} };
12031
+ }
12032
+ return out;
12033
+ }
12034
+ function typedValue(type, raw, label) {
12035
+ if (type === "number") {
12036
+ const n = Number(raw);
12037
+ if (!raw.trim() || !Number.isFinite(n))
12038
+ throw new UsageError("AIWS_USAGE", `${label}: a number`);
12039
+ return n;
12040
+ }
12041
+ if (type === "boolean") {
12042
+ if (raw !== "true" && raw !== "false")
12043
+ throw new UsageError("AIWS_USAGE", `${label}: true or false`);
12044
+ return raw === "true";
12045
+ }
12046
+ return raw;
12047
+ }
12048
+ async function exportTemplate(ctx, input) {
12049
+ const ws = requiredInputString(input, "workspace_id");
12050
+ const id = requiredInputString(input, "id");
12051
+ if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(id))
12052
+ throw new UsageError("AIWS_USAGE", `--id ${id}: [a-z0-9][a-z0-9-]{0,63}`);
12053
+ const description = inputString(input, "description");
12054
+ const param = inputString(input, "param");
12055
+ const template = {
12056
+ id,
12057
+ title: requiredInputString(input, "title"),
12058
+ ...description ? { description } : {},
12059
+ ...param ? { params: exportParams(param) } : {},
12060
+ ...inputString(input, "reset") ? { reset: { tables: list(input, "reset") } } : {}
12061
+ };
12062
+ const out = getHost().path.resolve(ctx.cwd, requiredInputString(input, "out"));
12063
+ const r = await aiws(ctx, "ext.export_template", { workspace_id: ws, surfaces: list(input, "surfaces"), ...inputString(input, "data") ? { data: list(input, "data") } : {}, template });
12064
+ const item = expectObject$4(r.template, "ext.export_template.template");
12065
+ const defs = (Array.isArray(r.controls) ? r.controls : []).map((c) => typeof c === "string" ? c : String(expectObject$4(c, "ext.export_template.controls[]").entity_id));
12066
+ const files = /* @__PURE__ */ new Map();
12067
+ let manifest = { manifest_version: AIWS_MANIFEST_VERSION, purpose: "aiws", host_api: {}, controls: [] };
12068
+ if (defs.length > 0) {
12069
+ const bundle = await exportBundle(extRpc(ctx), ws, defs, assetFetcher(ctx));
12070
+ for (const [path, bytes] of bundle.files)
12071
+ files.set(path, bytes);
12072
+ manifest = bundle.manifest;
12073
+ }
12074
+ const dir = templateDir(id);
12075
+ for (const f of Array.isArray(r.files) ? r.files : []) {
12076
+ const path = f.path.startsWith(dir) ? f.path : `${dir}${f.path}`;
12077
+ if (!isSourcePath(path))
12078
+ throw new ToolError("INVALID_SERVICE_RESPONSE", `ext.export_template: ${f.path} is not a file of the template`);
12079
+ files.set(path, fromBase64(f.base64));
12080
+ }
12081
+ manifest = { ...manifest, manifest_version: AIWS_MANIFEST_VERSION, workspace_templates: [item] };
12082
+ files.set(AIWS_MANIFEST_FILE, new TextEncoder().encode(`${JSON.stringify(manifest, null, 2)}
12083
+ `));
12084
+ const result = {
12085
+ ok: true,
12086
+ bundle: await writeBundle(out, files),
12087
+ template: { id: item.id, title: item.title, digest: item.digest, requires: item.requires },
12088
+ controls: controlSummary(manifest.controls)
12089
+ };
12090
+ return packBundle(ctx, input, out, id, result);
12091
+ }
12092
+ const templateCommand = {
12093
+ verb: "template",
12094
+ summary: "Workspace templates (snapshots carried in control packages): `list` (the templates of your library), `create <pkg_id>#<template_id>` (a new Workspace from one: answers its workspace_id), `export` (Surfaces of a Workspace as a template bundle; --pikg / --install pack it with the controls it needs and install it)",
12095
+ description: "create: --title names the new Workspace; --param k=v[,k=v…] sets the template’s params (typed as the template declares them; `template list` shows them). Installing a template never creates a Workspace; each create makes an independent one. export: --workspace, --surfaces s1,s2, --id and --title are required; --data e1,e2 carries data entities along, --param name:type[:default][,…] (type string | number | boolean) declares what a creator fills into {{params.<name>}}, --reset t1,t2 names tables a new Workspace starts without rows. It writes <out>/aiws/ (aiws-manifest.json at manifest_version 3 with the template in workspace_templates, controls/<dir>/ of the controls it needs, templates/<id>/), then packs and installs it as `ext-export` does.",
12096
+ positionals: [{ name: "action", description: "list | create | export", required: true }, { name: "target", description: "create: <pkg_id>#<template_id>", required: false }],
12097
+ options: [
12098
+ { name: "workspace", property: "workspace_id", description: "export: Workspace id (ws_…)", type: "string" },
12099
+ { name: "surfaces", property: "surfaces", description: "export: Surface ids, comma-separated", type: "string" },
12100
+ { name: "id", property: "id", description: "export: template id ([a-z0-9][a-z0-9-]{0,63})", type: "string" },
12101
+ { name: "title", property: "title", description: "create: title of the new Workspace; export: title of the template", type: "string" },
12102
+ { name: "description", property: "description", description: "export: what the template is for", type: "string" },
12103
+ { name: "data", property: "data", description: "export: data entity ids to carry along, comma-separated", type: "string" },
12104
+ { name: "param", property: "param", description: "create: k=v[,k=v…]; export: name:type[:default][,…]", type: "string" },
12105
+ { name: "reset", property: "reset", description: "export: tables a new Workspace starts without rows, comma-separated", type: "string" },
12106
+ { name: "out", property: "out", description: "export: local folder to write into (created)", type: "string" },
12107
+ ...packageOptions("the template id")
12108
+ ],
12109
+ inputSchema: {
12110
+ type: "object",
12111
+ properties: {
12112
+ action: { type: "string", enum: ["list", "create", "export"] },
12113
+ target: { type: "string" },
12114
+ workspace_id: { type: "string" },
12115
+ surfaces: { type: "string" },
12116
+ id: { type: "string" },
12117
+ title: { type: "string" },
12118
+ description: { type: "string" },
12119
+ data: { type: "string" },
12120
+ param: { type: "string" },
12121
+ reset: { type: "string" },
12122
+ out: { type: "string" },
12123
+ ...PACKAGE_INPUT
12124
+ },
12125
+ required: ["action"],
12126
+ additionalProperties: false
12127
+ },
12128
+ outputSchema: OBJECT_OUTPUT,
12129
+ resultSchemaVersion: 1,
12130
+ access: { mode: "fixed", level: "write" },
12131
+ asyncMode: "sync",
12132
+ requiresSession: true,
12133
+ examples: [
12134
+ "buckyos aiws template list",
12135
+ 'buckyos aiws template create team-pack.alice.bns.did#team-board --title "Ops board" --param team_name=Ops',
12136
+ 'buckyos aiws template export --workspace ws_abc --surfaces sf_board --id team-board --title "Team task board" --param team_name:string:Team --reset tasks --out ./team-pack --install'
12137
+ ],
12138
+ handler: async (ctx, input) => {
12139
+ const action = requiredInputString(input, "action");
12140
+ if (action === "list") {
12141
+ const library = await aiws(ctx, "ext.list_library", {});
12142
+ const packages = Array.isArray(library.packages) ? library.packages : [];
12143
+ return { ok: true, templates: packages.flatMap((p) => (p.templates ?? []).map((t) => ({ ref: `${p.pkg_id}#${t.id}`, pkg_id: p.pkg_id, version: p.version, ...t }))) };
12144
+ }
12145
+ if (action === "export")
12146
+ return exportTemplate(ctx, input);
12147
+ if (action !== "create")
12148
+ throw new UsageError("AIWS_USAGE", "template: list, create <pkg_id>#<template_id> or export --workspace <ws> --surfaces <ids> --id <id> --title <title> --out <dir>");
12149
+ const target = requiredInputString(input, "target");
12150
+ const cut = target.indexOf("#");
12151
+ if (cut <= 0 || cut === target.length - 1)
12152
+ throw new UsageError("AIWS_USAGE", `${target}: <pkg_id>#<template_id>`);
12153
+ const [pkg_id, template_id] = [target.slice(0, cut), target.slice(cut + 1)];
12154
+ const param = inputString(input, "param");
12155
+ let params;
12156
+ if (param) {
12157
+ const library = await aiws(ctx, "ext.list_library", {});
12158
+ const packages = Array.isArray(library.packages) ? library.packages : [];
12159
+ const found = packages.find((p) => p.pkg_id === pkg_id)?.templates?.find((t) => t.id === template_id);
12160
+ if (!found)
12161
+ throw new UsageError("AIWS_USAGE", `${target} is not a template of your library (aiws template list)`);
12162
+ params = createParams(param, found.params ?? {});
12163
+ }
12164
+ const title = inputString(input, "title");
12165
+ return aiws(ctx, "ext.create_from_template", { pkg_id, template_id, ...title ? { title } : {}, ...params ? { params } : {} });
12166
+ }
12167
+ };
11474
12168
  function createAiwsModule() {
11475
12169
  return {
11476
12170
  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]
12171
+ summary: "AI Workspace: read a workspace, Block-controls (catalogue, import, export), extension controls (build, pull, export, library, import, upgrade), Workspace templates (list, create, export), and execute wish runs as their executor (claim, heartbeat, report)",
12172
+ commands: [infoCommand, readCommand, queryCommand, controlsCommand, importCommand, exportCommand, buildCommand, pullCommand, extExportCommand, libraryCommand, extImportCommand, extUpgradeCommand, templateCommand, runGetCommand, runClaimCommand, runHeartbeatCommand, runFilesCommand, runToolCommand, runProgramCommand, runReportCommand]
11479
12173
  };
11480
12174
  }
11481
12175
  const VERSION = TOOL_VERSION;