buckyos 0.7.160 → 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, 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";
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";
@@ -4499,7 +4499,11 @@ async function verifyAiwsPayload(tarGz) {
4499
4499
  if (errors.length > 0) {
4500
4500
  throw invalid("aiws", `aiws bundle: ${errors.map((d) => `${d.file}: ${d.message}`).join("; ")}`);
4501
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 })) };
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
+ };
4503
4507
  }
4504
4508
  async function validateSnapshot(distDir, appDoc, packageMeta) {
4505
4509
  const payloads = /* @__PURE__ */ new Map();
@@ -5238,7 +5242,7 @@ function validateContentHandlers(value, permissions) {
5238
5242
  }
5239
5243
  return value;
5240
5244
  }
5241
- const PACKAGE_VERSION = "0.7.160";
5245
+ const PACKAGE_VERSION = "0.7.162";
5242
5246
  const TOOL_VERSION = PACKAGE_VERSION;
5243
5247
  const SDK_VERSION = PACKAGE_VERSION;
5244
5248
  const PROTOCOL_VERSION = "1";
@@ -5332,7 +5336,12 @@ function createPikgModule(dependencies = {}) {
5332
5336
  type: "string"
5333
5337
  },
5334
5338
  { name: "version", description: "Initial App version", type: "string" },
5335
- { 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
+ }
5336
5345
  ],
5337
5346
  inputSchema: {
5338
5347
  type: "object",
@@ -5343,7 +5352,8 @@ function createPikgModule(dependencies = {}) {
5343
5352
  kind: { type: "string", enum: APP_KINDS },
5344
5353
  source: { type: "string", minLength: 1 },
5345
5354
  version: { type: "string", minLength: 1 },
5346
- app_did: { type: "string", minLength: 1 }
5355
+ app_did: { type: "string", minLength: 1 },
5356
+ composite: { type: "boolean" }
5347
5357
  },
5348
5358
  additionalProperties: false
5349
5359
  },
@@ -5358,7 +5368,8 @@ function createPikgModule(dependencies = {}) {
5358
5368
  "buckyos pikg init .",
5359
5369
  "buckyos --non-interactive pikg init . --owner did:bns:root --kind static-web --source ./web/dist",
5360
5370
  "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"
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"
5362
5373
  ],
5363
5374
  handler: async (ctx, input) => await initCommand(ctx, input, docker)
5364
5375
  },
@@ -5601,7 +5612,7 @@ async function initCommand(ctx, input, docker) {
5601
5612
  if (!isHostError(error, "NotFound"))
5602
5613
  throw error;
5603
5614
  if (kind === "aiws-controls") {
5604
- await writeAiwsSkeleton(absoluteSource, owner, name);
5615
+ await writeAiwsSkeleton(absoluteSource, owner, name, input.composite === true);
5605
5616
  sourceSummary2.skeleton = true;
5606
5617
  } else {
5607
5618
  await ctx.io.stderr(`warning: source does not exist yet: ${sourceSummary2.path}
@@ -5666,8 +5677,9 @@ async function initCommand(ctx, input, docker) {
5666
5677
  next_command: `buckyos pikg build ${metaDir}`
5667
5678
  };
5668
5679
  }
5669
- async function writeAiwsSkeleton(dir, ownerDid, name) {
5670
- const files = controlSkeleton({ authorDid: ownerDid, name, title: name });
5680
+ async function writeAiwsSkeleton(dir, ownerDid, name, composite) {
5681
+ const kind = composite ? "composite" : "declarative";
5682
+ const files = controlSkeleton({ authorDid: ownerDid, name, title: name, kind });
5671
5683
  const encoder = new TextEncoder();
5672
5684
  const host2 = getHost();
5673
5685
  await host2.mkdir(join(dir, "controls", name), { recursive: true });
@@ -5677,8 +5689,8 @@ async function writeAiwsSkeleton(dir, ownerDid, name) {
5677
5689
  const manifest = {
5678
5690
  manifest_version: AIWS_MANIFEST_VERSION,
5679
5691
  purpose: "aiws",
5680
- host_api: { declarative: 1 },
5681
- controls: [{ ident: `${ownerDid}#${name}`, name, kind: "declarative", version: 1, digest, dir: `controls/${name}/`, title: name }],
5692
+ host_api: composite ? {} : { declarative: 1 },
5693
+ controls: [{ ident: `${ownerDid}#${name}`, name, kind, version: 1, digest, dir: `controls/${name}/`, title: name }],
5682
5694
  workspace_templates: []
5683
5695
  };
5684
5696
  await host2.writeTextFile(join(dir, AIWS_MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
@@ -11577,6 +11589,25 @@ async function sourceErrors(run) {
11577
11589
  function list(input, key) {
11578
11590
  return requiredInputString(input, key).split(",").map((s) => s.trim()).filter(Boolean);
11579
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
+ }
11580
11611
  const LOCAL_STATE = ".aiws-build.json";
11581
11612
  async function isLocalDir(path) {
11582
11613
  try {
@@ -11589,25 +11620,42 @@ async function localFiles(dir) {
11589
11620
  const host2 = getHost();
11590
11621
  const files = [];
11591
11622
  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;
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" });
11599
11637
  }
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
- }
11638
+ };
11639
+ await walk(dir, "", 0);
11604
11640
  if (files.length === 0)
11605
11641
  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
11642
  if (problems.length > 0)
11609
11643
  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));
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
+ };
11611
11659
  }
11612
11660
  async function localState(dir) {
11613
11661
  try {
@@ -11620,11 +11668,12 @@ async function buildLocal(ctx, ws, dir, opts) {
11620
11668
  const state = await localState(dir);
11621
11669
  const result = await aiws(ctx, "ext.build", {
11622
11670
  workspace_id: ws,
11623
- files: await localFiles(dir),
11671
+ files: await sourceErrors(async () => packBuildFiles(await localFiles(dir), sourceUploader(ctx))),
11624
11672
  ...opts.folderId ? { folder_id: opts.folderId } : {},
11625
11673
  ...state && state.workspace_id === ws ? { expect: { def_rev: state.def_rev, dir_rev: state.dir_rev } } : {},
11626
11674
  ...opts.force ? { force: true } : {},
11627
- ...opts.dryRun ? { dry_run: true } : {}
11675
+ ...opts.dryRun ? { dry_run: true } : {},
11676
+ ...opts.migration ? { migration: opts.migration } : {}
11628
11677
  });
11629
11678
  if (!opts.dryRun && typeof result.def_entity_id === "string" && typeof result.def_rev === "number") {
11630
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)}
@@ -11635,7 +11684,7 @@ async function buildLocal(ctx, ws, dir, opts) {
11635
11684
  const buildCommand = {
11636
11685
  verb: "build",
11637
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",
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.",
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).',
11639
11688
  positionals: [{ name: "dir", description: "Local folder, or Zone path of the control source folder", required: true }],
11640
11689
  options: [
11641
11690
  WORKSPACE_OPTION,
@@ -11643,9 +11692,10 @@ const buildCommand = {
11643
11692
  { name: "watch", property: "watch", description: "Rebuild whenever the folder changes", type: "boolean" },
11644
11693
  { name: "dry-run", property: "dry_run", description: "Check only: diagnostics, nothing committed", type: "boolean" },
11645
11694
  { 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" }
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" }
11647
11697
  ],
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"]),
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"]),
11649
11699
  outputSchema: OBJECT_OUTPUT,
11650
11700
  resultSchemaVersion: 1,
11651
11701
  access: { mode: "fixed", level: "write" },
@@ -11655,7 +11705,8 @@ const buildCommand = {
11655
11705
  handler: async (ctx, input) => {
11656
11706
  const ws = requiredInputString(input, "workspace_id");
11657
11707
  const dir = requiredInputString(input, "dir");
11658
- const opts = { folderId: inputString(input, "folder_id"), force: input.force === true, dryRun: input.dry_run === true };
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) } : {} };
11659
11710
  if (input.zone_path !== true && await isLocalDir(getHost().path.resolve(ctx.cwd, dir))) {
11660
11711
  const local = getHost().path.resolve(ctx.cwd, dir);
11661
11712
  if (input.watch === true)
@@ -11707,7 +11758,8 @@ const pullCommand = {
11707
11758
  const fetchAsset = assetFetcher(ctx);
11708
11759
  const files = [];
11709
11760
  for (const f of control.files) {
11710
- const target = host2.path.join(out, f.name);
11761
+ const target = host2.path.join(out, ...f.name.split("/"));
11762
+ await host2.mkdir(host2.path.dirname(target), { recursive: true });
11711
11763
  if (f.text !== void 0)
11712
11764
  await host2.writeTextFile(target, f.text);
11713
11765
  else
@@ -11741,6 +11793,57 @@ async function uploadPackage(ctx, path) {
11741
11793
  throw new ToolError("AIWS_UPLOAD_FAILED", `upload of ${path}: HTTP ${response.status} ${await response.text().catch(() => "")}`);
11742
11794
  return { bucket_id, key };
11743
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 }));
11744
11847
  const extExportCommand = {
11745
11848
  verb: "ext-export",
11746
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",
@@ -11749,13 +11852,9 @@ const extExportCommand = {
11749
11852
  options: [
11750
11853
  WORKSPACE_OPTION,
11751
11854
  { 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" }
11855
+ ...packageOptions("the first control’s")
11757
11856
  ],
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"]),
11857
+ inputSchema: workspaceInput({ defs: { type: "string", minLength: 1 }, out: { type: "string", minLength: 1 }, ...PACKAGE_INPUT }, ["defs", "out"]),
11759
11858
  outputSchema: OBJECT_OUTPUT,
11760
11859
  resultSchemaVersion: 1,
11761
11860
  access: { mode: "fixed", level: "write" },
@@ -11763,97 +11862,314 @@ const extExportCommand = {
11763
11862
  requiresSession: true,
11764
11863
  examples: ["buckyos aiws ext-export def_abc,def_xyz --workspace ws_abc --out ./kpi-pack --install"],
11765
11864
  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;
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
+ });
11803
11901
  }
11804
11902
  };
11805
11903
  const libraryCommand = {
11806
11904
  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 },
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 },
11811
11912
  outputSchema: OBJECT_OUTPUT,
11812
11913
  resultSchemaVersion: 1,
11813
11914
  access: { mode: "fixed", level: "write" },
11814
11915
  asyncMode: "sync",
11815
11916
  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"],
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"],
11817
11918
  handler: async (ctx, input) => {
11818
11919
  const action = requiredInputString(input, "action");
11819
11920
  if (action === "list")
11820
11921
  return aiws(ctx, "ext.list_library", {});
11821
11922
  if (action === "remove")
11822
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
+ }
11823
11934
  if (action !== "install")
11824
- throw new UsageError("AIWS_USAGE", "library: list, install <file.pikg> or remove <pkg_id>");
11935
+ throw new UsageError("AIWS_USAGE", "library: list, install <file.pikg>, remove <pkg_id> or bundle <pkg_id> --out <file.json>");
11825
11936
  const path = getHost().path.resolve(ctx.cwd, requiredInputString(input, "target"));
11826
11937
  return aiws(ctx, "ext.install", { pikg: await uploadPackage(ctx, path), mode: "dev" });
11827
11938
  }
11828
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
+ }
11829
11965
  const extImportCommand = {
11830
11966
  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"]),
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" } }),
11835
11978
  outputSchema: OBJECT_OUTPUT,
11836
11979
  resultSchemaVersion: 1,
11837
11980
  access: { mode: "fixed", level: "write" },
11838
11981
  asyncMode: "sync",
11839
11982
  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
- });
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");
11848
11986
  const folder = inputString(input, "folder_id");
11849
- return aiws(ctx, "ext.import", { workspace_id: requiredInputString(input, "workspace_id"), items, ...folder ? { folder_id: folder } : {} });
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 } : {} });
11850
12166
  }
11851
12167
  };
11852
12168
  function createAiwsModule() {
11853
12169
  return {
11854
12170
  name: "aiws",
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]
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]
11857
12173
  };
11858
12174
  }
11859
12175
  const VERSION = TOOL_VERSION;