busa-cli 0.10.1 → 0.10.3

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.
@@ -234,11 +234,11 @@ var baseFieldChangeTask = {
234
234
  ...input.message ? { message: input.message } : {},
235
235
  ...input.submittedBy ? { submittedBy: input.submittedBy } : {}
236
236
  };
237
- const bases = client.bases;
238
237
  switch (input.operation) {
239
238
  case "add":
240
- return bases.createFieldChangeRequest({
239
+ return client.bases.fieldChangeRequest({
241
240
  ...common,
241
+ operation: "create",
242
242
  slug: input.slug,
243
243
  name: input.name,
244
244
  ...input.fieldType ? { type: input.fieldType } : {},
@@ -246,31 +246,36 @@ var baseFieldChangeTask = {
246
246
  ...input.options ? { options: input.options } : {}
247
247
  });
248
248
  case "update":
249
- return bases.updateFieldChangeRequest({
249
+ return client.bases.fieldChangeRequest({
250
250
  ...common,
251
+ operation: "update",
251
252
  fieldId: input.fieldId,
252
253
  patch: input.patch
253
254
  });
254
255
  case "delete":
255
- return bases.deleteFieldChangeRequest({
256
+ return client.bases.fieldChangeRequest({
256
257
  ...common,
258
+ operation: "delete",
257
259
  fieldId: input.fieldId
258
260
  });
259
261
  case "convert":
260
- return bases.convertFieldChangeRequest({
262
+ return client.bases.fieldChangeRequest({
261
263
  ...common,
264
+ operation: "convert",
262
265
  fieldId: input.fieldId,
263
266
  newType: input.newType,
264
267
  ...input.selectChoiceMode ? { selectChoiceMode: input.selectChoiceMode } : {}
265
268
  });
266
269
  case "reorder":
267
- return bases.reorderFieldsChangeRequest({
270
+ return client.bases.fieldChangeRequest({
268
271
  ...common,
272
+ operation: "reorder",
269
273
  fieldIds: input.fieldIds
270
274
  });
271
275
  default:
272
- return bases.restoreFieldChangeRequest({
276
+ return client.bases.fieldChangeRequest({
273
277
  ...common,
278
+ operation: "restore",
274
279
  fieldId: input.fieldId
275
280
  });
276
281
  }
@@ -313,7 +318,7 @@ var changeRequestQueryTask = {
313
318
  ],
314
319
  execute: async (client, input) => {
315
320
  if (input.countsOnly) return client.changeRequests.counts();
316
- return client.changeRequests.listPaged({
321
+ return client.changeRequests.list({
317
322
  ...input.status ? { status: input.status } : {},
318
323
  ...input.mine !== void 0 ? { mine: input.mine } : {},
319
324
  ...input.limit !== void 0 ? { limit: input.limit } : {},
@@ -403,11 +408,6 @@ var changeRequestMergeTask = {
403
408
 
404
409
  // ../../packages/busabase-contract/src/tasks/file-tree.ts
405
410
  var FILE_TREE_KINDS = ["skill", "drive", "airapp"];
406
- var NAMESPACE = {
407
- skill: "skills",
408
- drive: "drives",
409
- airapp: "airapps"
410
- };
411
411
  var kindParam = {
412
412
  name: "kind",
413
413
  kind: "enum",
@@ -452,7 +452,7 @@ var nodeListTask = {
452
452
  annotations: { readOnly: true, destructive: false },
453
453
  params: [kindParam],
454
454
  examples: ["busabase-cli nodes list-file-trees --kind skill"],
455
- execute: async (client, input) => client[NAMESPACE[resolveKind(input.kind)]].list()
455
+ execute: async (client, input) => client.fileTrees.list({ type: resolveKind(input.kind) })
456
456
  };
457
457
  var nodeGetTask = {
458
458
  name: "node_get_file_tree",
@@ -478,7 +478,7 @@ var nodeGetTask = {
478
478
  annotations: { readOnly: true, destructive: false },
479
479
  params: [kindParam, nodeIdParam],
480
480
  examples: ["busabase-cli nodes get-file-tree --kind airapp --node-id nod_123"],
481
- execute: async (client, input) => client[NAMESPACE[resolveKind(input.kind)]].get({ nodeId: input.nodeId })
481
+ execute: async (client, input) => client.fileTrees.get({ nodeId: input.nodeId, type: resolveKind(input.kind) })
482
482
  };
483
483
  var nodeListFilesTask = {
484
484
  name: "node_files_list",
@@ -504,7 +504,7 @@ var nodeListFilesTask = {
504
504
  annotations: { readOnly: true, destructive: false },
505
505
  params: [kindParam, nodeIdParam],
506
506
  examples: ["busabase-cli nodes files --kind drive --node-id nod_123"],
507
- execute: async (client, input) => client[NAMESPACE[resolveKind(input.kind)]].listFiles({ nodeId: input.nodeId })
507
+ execute: async (client, input) => client.fileTrees.listFiles({ nodeId: input.nodeId, type: resolveKind(input.kind) })
508
508
  };
509
509
  var nodeReadFileTask = {
510
510
  name: "node_file_read",
@@ -540,9 +540,10 @@ var nodeReadFileTask = {
540
540
  }
541
541
  ],
542
542
  examples: ["busabase-cli nodes read-file --kind skill --node-id nod_123 --file-path SKILL.md"],
543
- execute: async (client, input) => client[NAMESPACE[resolveKind(input.kind)]].readFile({
543
+ execute: async (client, input) => client.fileTrees.readFile({
544
544
  nodeId: input.nodeId,
545
- filePath: input.filePath
545
+ filePath: input.filePath,
546
+ type: resolveKind(input.kind)
546
547
  })
547
548
  };
548
549
  var nodeFilesChangeRequestTask = {
@@ -593,11 +594,12 @@ var nodeFilesChangeRequestTask = {
593
594
  }
594
595
  const payload = {
595
596
  nodeId: input.nodeId,
597
+ type: resolveKind(input.kind),
596
598
  operations: input.operations,
597
599
  ...input.message ? { message: input.message } : {},
598
600
  ...input.submittedBy ? { submittedBy: input.submittedBy } : {}
599
601
  };
600
- return client[NAMESPACE[resolveKind(input.kind)]].createChangeRequest(payload);
602
+ return client.fileTrees.createChangeRequest(payload);
601
603
  }
602
604
  };
603
605
 
@@ -655,15 +657,18 @@ var listArchivedTask = {
655
657
  "busabase-cli archived list --scope records --base-id bas_1 --limit 100"
656
658
  ],
657
659
  execute: async (client, input) => {
658
- if (input.scope === "nodes") return client.nodes.listArchived();
659
- if (input.scope === "bases") return client.bases.listArchived();
660
+ if (input.scope === "nodes") return client.nodes.list({ status: "archived" });
661
+ if (input.scope === "bases") return client.bases.list({ status: "archived" });
660
662
  if (!input.baseId) {
661
663
  throw new Error(`scope "${input.scope}" requires baseId.`);
662
664
  }
663
665
  if (input.scope === "fields") return client.bases.listDeletedFields({ baseId: input.baseId });
664
- if (input.scope === "views") return client.bases.listArchivedViews({ baseId: input.baseId });
665
- return client.bases.listArchivedRecordsPaged({
666
+ if (input.scope === "views") {
667
+ return client.bases.listViews({ baseId: input.baseId, status: "archived" });
668
+ }
669
+ return client.records.list({
666
670
  baseId: input.baseId,
671
+ status: "archived",
667
672
  ...input.limit !== void 0 ? { limit: input.limit } : {},
668
673
  ...input.cursor ? { cursor: input.cursor } : {}
669
674
  });
@@ -1237,14 +1242,15 @@ var nodeCreateTask = {
1237
1242
  if (FILE_TREE_TYPES.has(type)) {
1238
1243
  const payload = {
1239
1244
  ...common,
1245
+ // `type` is the contract's discriminator, so the narrowing has to be
1246
+ // explicit — `FILE_TREE_TYPES.has` doesn't narrow a plain string.
1247
+ type,
1240
1248
  ...Array.isArray(input.files) ? { files: input.files } : {},
1241
1249
  ...input.mergeMode ? { mergeMode: input.mergeMode } : {},
1242
1250
  ...input.visibility ? { visibility: input.visibility } : {},
1243
1251
  ...input.version ? { version: input.version } : {}
1244
1252
  };
1245
- if (type === "skill") return client.skills.create(payload);
1246
- if (type === "drive") return client.drives.create(payload);
1247
- return client.airapps.create(payload);
1253
+ return client.fileTrees.create(payload);
1248
1254
  }
1249
1255
  if (type === "doc") {
1250
1256
  const payload = { ...common, body: input.body ?? "" };
@@ -1321,20 +1327,20 @@ var recordChangeTask = {
1321
1327
  if (!input.fields || typeof input.fields !== "object") {
1322
1328
  throw new Error('operation "update" requires `fields` (an object keyed by field slug).');
1323
1329
  }
1324
- return client.records.updateChangeRequest({
1330
+ return client.records.changeRequest({
1325
1331
  ...common,
1332
+ operation: "update",
1326
1333
  fields: input.fields
1327
1334
  });
1328
1335
  }
1329
1336
  if (input.operation === "delete") {
1330
- return client.records.deleteChangeRequest({
1337
+ return client.records.changeRequest({
1331
1338
  ...common,
1339
+ operation: "delete",
1332
1340
  deleteMode: "archive"
1333
1341
  });
1334
1342
  }
1335
- return client.records.restoreChangeRequest(
1336
- common
1337
- );
1343
+ return client.records.changeRequest({ ...common, operation: "restore" });
1338
1344
  }
1339
1345
  };
1340
1346
 
@@ -1415,7 +1421,7 @@ var recordQueryTask = {
1415
1421
  input.baseId ? { baseId: input.baseId } : {}
1416
1422
  );
1417
1423
  }
1418
- return client.records.listPaged({
1424
+ return client.records.list({
1419
1425
  ...input.limit !== void 0 ? { limit: input.limit } : {},
1420
1426
  ...input.baseId ? { baseId: input.baseId } : {},
1421
1427
  ...input.cursor ? { cursor: input.cursor } : {},
@@ -1493,7 +1499,8 @@ var viewChangeTask = {
1493
1499
  if (!input.baseId || !input.name) {
1494
1500
  throw new Error('action "create" requires baseId and name.');
1495
1501
  }
1496
- return client.bases.createViewChangeRequest({
1502
+ return client.views.changeRequest({
1503
+ operation: "create",
1497
1504
  baseId: input.baseId,
1498
1505
  name: input.name,
1499
1506
  ...input.type ? { type: input.type } : {},
@@ -1505,19 +1512,16 @@ var viewChangeTask = {
1505
1512
  const target = { viewId: input.viewId, ...meta };
1506
1513
  if (input.action === "update") {
1507
1514
  if (!input.patch) throw new Error('action "update" requires patch.');
1508
- return client.views.updateChangeRequest({
1515
+ return client.views.changeRequest({
1516
+ operation: "update",
1509
1517
  ...target,
1510
1518
  ...input.patch
1511
1519
  });
1512
1520
  }
1513
1521
  if (input.action === "delete") {
1514
- return client.views.deleteChangeRequest(
1515
- target
1516
- );
1522
+ return client.views.changeRequest({ operation: "delete", ...target });
1517
1523
  }
1518
- return client.views.restoreChangeRequest(
1519
- target
1520
- );
1524
+ return client.views.changeRequest({ operation: "restore", ...target });
1521
1525
  }
1522
1526
  };
1523
1527
 
@@ -1552,6 +1556,8 @@ import {
1552
1556
  cloudContract,
1553
1557
  createBusabaseClient,
1554
1558
  DEFAULT_BASE_URL as DEFAULT_BASE_URL2,
1559
+ getRecordByField,
1560
+ grepAssets,
1555
1561
  normalizeBaseUrl as normalizeBaseUrl2
1556
1562
  } from "busabase-sdk";
1557
1563
  import {
@@ -2482,15 +2488,15 @@ var reportProgress = (message) => {
2482
2488
  };
2483
2489
  var STATE_ONLY_BACKUP_MESSAGE = [
2484
2490
  "--state-only backup is not implemented, and will not be \u2014 it is superseded by",
2485
- "`busabase-cli publish`, which writes the same state-level content as a readable,",
2491
+ "`busabase-cli export`, which writes the same state-level content as a readable,",
2486
2492
  "diffable directory you can push to GitHub:",
2487
2493
  "",
2488
- " busabase-cli publish <node-slug> -o ./my-package",
2494
+ " busabase-cli export <node-slug> -o ./my-package",
2489
2495
  " busabase-cli install https://github.com/<you>/<repo>",
2490
2496
  "",
2491
2497
  "`busabase-cli backup` stays the full-fidelity backup path (raw rows, original ids,",
2492
2498
  "history) \u2014 run it without --state-only. The two formats are deliberately separate:",
2493
- "a backup carries the audit events, comments, reviews and ACLs that a published",
2499
+ "a backup carries the audit events, comments, reviews and ACLs that an exported",
2494
2500
  "package must never leak."
2495
2501
  ].join("\n");
2496
2502
  var STATE_ONLY_RESTORE_MESSAGE = [
@@ -3393,6 +3399,15 @@ var restoreFieldChangeRequestInputSchema = z6.object({
3393
3399
  message: z6.string().optional(),
3394
3400
  submittedBy: z6.string().optional().default("local-editor")
3395
3401
  });
3402
+ var withBaseId = { baseId: z6.string().min(1) };
3403
+ var fieldChangeRequestInputSchema = z6.discriminatedUnion("operation", [
3404
+ createFieldChangeRequestInputSchema.extend({ operation: z6.literal("create"), ...withBaseId }),
3405
+ updateFieldChangeRequestInputSchema.extend({ operation: z6.literal("update"), ...withBaseId }),
3406
+ deleteFieldChangeRequestInputSchema.extend({ operation: z6.literal("delete"), ...withBaseId }),
3407
+ convertFieldChangeRequestInputSchema.extend({ operation: z6.literal("convert"), ...withBaseId }),
3408
+ reorderFieldsChangeRequestInputSchema.extend({ operation: z6.literal("reorder"), ...withBaseId }),
3409
+ restoreFieldChangeRequestInputSchema.extend({ operation: z6.literal("restore"), ...withBaseId })
3410
+ ]);
3396
3411
 
3397
3412
  // ../../packages/busabase-contract/src/domains/base/contract/view-schemas.ts
3398
3413
  import { z as z8 } from "zod";
@@ -3444,7 +3459,13 @@ var listNodesInputSchema = z7.object({
3444
3459
  parentId: z7.string().nullable().optional().describe("Node to start from. Omit or null to start from the space root."),
3445
3460
  depth: z7.coerce.number().int().min(1).max(5).optional().describe(
3446
3461
  "How many levels beneath the start point to eagerly include (default 2 once either field is set). Capped at 5."
3447
- )
3462
+ ),
3463
+ /**
3464
+ * `active` (default) walks the live tree. `archived` returns the flat set of
3465
+ * soft-archived nodes for the Trash view — no `parentId`/`depth` walk, since
3466
+ * archived nodes are shown as a list, not a tree.
3467
+ */
3468
+ status: z7.enum(["active", "archived"]).optional().default("active")
3448
3469
  }).optional();
3449
3470
  var isDescendantInputSchema = z7.object({
3450
3471
  nodeId: z7.string(),
@@ -3773,6 +3794,9 @@ var createCommentInputSchema = commentSubjectInputSchema.extend({
3773
3794
  var listInputSchema = z7.object({
3774
3795
  limit: z7.coerce.number().int().min(1).max(100).optional().default(50)
3775
3796
  }).optional().default({ limit: 50 });
3797
+ var listByStatusInputSchema = z7.object({
3798
+ status: z7.enum(["active", "archived"]).optional().default("active")
3799
+ });
3776
3800
  var listChangeRequestsPagedInputSchema = z7.object({
3777
3801
  limit: z7.coerce.number().int().min(1).max(100).optional().default(50),
3778
3802
  /** Opaque base64 cursor (`createdAt|id`) for keyset pagination. */
@@ -3949,6 +3973,24 @@ var restoreViewInputSchema = z8.object({
3949
3973
  message: z8.string().optional().default("Restore view"),
3950
3974
  submittedBy: z8.string().optional().default("local-producer")
3951
3975
  });
3976
+ var viewChangeRequestInputSchema = z8.discriminatedUnion("operation", [
3977
+ createViewInputSchema.extend({
3978
+ operation: z8.literal("create"),
3979
+ baseId: z8.string().min(1).describe("Base the new view belongs to.")
3980
+ }),
3981
+ updateViewInputSchema.extend({
3982
+ operation: z8.literal("update"),
3983
+ viewId: z8.string().min(1)
3984
+ }),
3985
+ deleteViewInputSchema.extend({
3986
+ operation: z8.literal("delete"),
3987
+ viewId: z8.string().min(1)
3988
+ }),
3989
+ restoreViewInputSchema.extend({
3990
+ operation: z8.literal("restore"),
3991
+ viewId: z8.string().min(1)
3992
+ })
3993
+ ]);
3952
3994
 
3953
3995
  // ../../packages/busabase-contract/src/domains/package/types.ts
3954
3996
  var PACKAGE_FORMAT = "busabase-package@1";
@@ -3969,7 +4011,7 @@ var PACKAGE_COMPUTED_FIELD_TYPES = [
3969
4011
  "auto_number",
3970
4012
  "formula",
3971
4013
  // `lookup` is derived from OTHER records and resolved at read time, so it
3972
- // shows up in `headCommit.fields` like any other value — but publishing it
4014
+ // shows up in `headCommit.fields` like any other value — but exporting it
3973
4015
  // would ship derived data as if it were authored, and install would strip it
3974
4016
  // server-side anyway.
3975
4017
  "lookup",
@@ -4292,7 +4334,8 @@ var applyInstall = async (client, plan, options) => {
4292
4334
  }
4293
4335
  const patched = resolveFieldIdOptions(field, base.node.slug, fieldIds, state.warnings);
4294
4336
  if (!patched) continue;
4295
- const changeRequest = await client.bases.updateFieldChangeRequest({
4337
+ const changeRequest = await client.bases.fieldChangeRequest({
4338
+ operation: "update",
4296
4339
  baseId: base.baseId,
4297
4340
  fieldId,
4298
4341
  patch: { options: patched },
@@ -4443,7 +4486,8 @@ var createBase = async (client, node, parentNodeId, options, state, bases, field
4443
4486
  bases.push({ node, baseId: result.id });
4444
4487
  indexFields(fieldIds, node.slug, result.fields);
4445
4488
  for (const view of node.base.views) {
4446
- const changeRequest = await client.bases.createViewChangeRequest({
4489
+ const changeRequest = await client.views.changeRequest({
4490
+ operation: "create",
4447
4491
  baseId: result.id,
4448
4492
  slug: view.slug,
4449
4493
  name: view.name,
@@ -4477,8 +4521,8 @@ var createFileTreeNode = async (client, node, parentNodeId, options, state) => {
4477
4521
  })
4478
4522
  );
4479
4523
  const files = uploaded.filter((entry) => entry !== void 0);
4480
- const api = node.type === "skill" ? client.skills : node.type === "airapp" ? client.airapps : client.drives;
4481
- const result = await api.create({
4524
+ const result = await client.fileTrees.create({
4525
+ type: node.type,
4482
4526
  parentNodeId,
4483
4527
  slug: node.slug,
4484
4528
  name: node.name,
@@ -4653,7 +4697,8 @@ var linkRelations = async (client, base, relationFieldSlugs, recordIdsByKey, opt
4653
4697
  if (!hasRelation) continue;
4654
4698
  const recordId = recordIdsByKey.get(record.key);
4655
4699
  if (!recordId) continue;
4656
- const changeRequest = await client.records.updateChangeRequest({
4700
+ const changeRequest = await client.records.changeRequest({
4701
+ operation: "update",
4657
4702
  recordId,
4658
4703
  fields: { ...toCreatableFields(base.node, record.fields), ...relationValues },
4659
4704
  message: `Link relations for ${base.node.slug}`,
@@ -4710,7 +4755,7 @@ var collectNode = async (client, source, options) => {
4710
4755
  slug: source.slug,
4711
4756
  name: source.name,
4712
4757
  description: source.description ?? "",
4713
- // `publish` always writes position, so round trips preserve sibling order exactly.
4758
+ // `export` always writes position, so round trips preserve sibling order exactly.
4714
4759
  position: source.position
4715
4760
  };
4716
4761
  switch (source.type) {
@@ -4729,7 +4774,7 @@ var collectNode = async (client, source, options) => {
4729
4774
  case "skill":
4730
4775
  case "airapp":
4731
4776
  case "drive":
4732
- return collectFileTree(client, source, common, options);
4777
+ return collectFileTree(client, source, source.type, common, options);
4733
4778
  case "file":
4734
4779
  return collectFile(client, source, common, options);
4735
4780
  default:
@@ -4742,7 +4787,7 @@ var collectBase = async (client, source, common, options) => {
4742
4787
  const base = await client.bases.get({ baseId });
4743
4788
  if (!base) {
4744
4789
  throw new Error(
4745
- `Base node "${source.slug}" has no Base behind it (looked up ${baseId}). The space may be mid-migration; re-run publish.`
4790
+ `Base node "${source.slug}" has no Base behind it (looked up ${baseId}). The space may be mid-migration; re-run export.`
4746
4791
  );
4747
4792
  }
4748
4793
  const views = await client.bases.listViews({ baseId: base.id });
@@ -4771,7 +4816,7 @@ var collectBase = async (client, source, common, options) => {
4771
4816
  };
4772
4817
  };
4773
4818
  var buildIndexes = async (client) => {
4774
- const bases = await client.bases.list();
4819
+ const bases = await client.bases.list({});
4775
4820
  const baseSlugById = /* @__PURE__ */ new Map();
4776
4821
  const fieldSlugById = /* @__PURE__ */ new Map();
4777
4822
  for (const base of bases) {
@@ -4793,7 +4838,7 @@ var toPackageField = (field, context) => {
4793
4838
  const targetSlug = context.baseSlugById.get(targetBaseId);
4794
4839
  if (!targetSlug) {
4795
4840
  throw new Error(
4796
- `Field "${context.baseSlug}.${field.slug}" relates to a Base that no longer exists (${targetBaseId}). Fix the field before publishing.`
4841
+ `Field "${context.baseSlug}.${field.slug}" relates to a Base that no longer exists (${targetBaseId}). Fix the field before exporting.`
4797
4842
  );
4798
4843
  }
4799
4844
  options.targetBaseSlug = targetSlug;
@@ -4858,7 +4903,7 @@ var collectRecords = async (client, baseId, fields, options) => {
4858
4903
  let droppedAttachments = 0;
4859
4904
  let cursor;
4860
4905
  do {
4861
- const page = await client.records.listPaged({ baseId, limit: 100, cursor });
4906
+ const page = await client.records.list({ baseId, limit: 100, cursor });
4862
4907
  for (const record of page.records) {
4863
4908
  if (record.status !== "active") continue;
4864
4909
  const values = {};
@@ -4884,12 +4929,15 @@ var collectRecords = async (client, baseId, fields, options) => {
4884
4929
  }
4885
4930
  return records.sort((a, b) => a.key.localeCompare(b.key, "en"));
4886
4931
  };
4887
- var collectFileTree = async (client, source, common, options) => {
4888
- const api = source.type === "skill" ? client.skills : source.type === "airapp" ? client.airapps : client.drives;
4889
- const listed = await api.listFiles({ nodeId: source.id });
4932
+ var collectFileTree = async (client, source, type, common, options) => {
4933
+ const listed = await client.fileTrees.listFiles({ nodeId: source.id, type });
4890
4934
  const files = [];
4891
4935
  for (const file of listed) {
4892
- const read = await api.readFile({ nodeId: source.id, filePath: file.path });
4936
+ const read = await client.fileTrees.readFile({
4937
+ nodeId: source.id,
4938
+ filePath: file.path,
4939
+ type
4940
+ });
4893
4941
  files.push({
4894
4942
  path: file.path,
4895
4943
  bytes: read.encoding === "url" ? await downloadBytes(read.content, file.path, options) : Buffer.from(read.content, "utf8")
@@ -4940,7 +4988,7 @@ var assertSelfContained = (tree, options) => {
4940
4988
  if (field.type !== "relation" || !targetBaseSlug) continue;
4941
4989
  if (!packagedBaseSlugs.has(targetBaseSlug)) {
4942
4990
  throw new Error(
4943
- `Field "${node.slug}.${field.slug}" relates to Base "${targetBaseSlug}", which is outside the subtree being published. A package must be self-contained \u2014 publish a subtree that includes both Bases, or remove the field.`
4991
+ `Field "${node.slug}.${field.slug}" relates to Base "${targetBaseSlug}", which is outside the subtree being exported. A package must be self-contained \u2014 export a subtree that includes both Bases, or remove the field.`
4944
4992
  );
4945
4993
  }
4946
4994
  }
@@ -4957,7 +5005,7 @@ var assertSelfContained = (tree, options) => {
4957
5005
  }
4958
5006
  if (dangling > 0) {
4959
5007
  options.warn(
4960
- `Base "${node.slug}" has ${dangling} relation value(s) pointing at records outside the published subtree \u2014 they were dropped.`
5008
+ `Base "${node.slug}" has ${dangling} relation value(s) pointing at records outside the exported subtree \u2014 they were dropped.`
4961
5009
  );
4962
5010
  }
4963
5011
  }
@@ -5588,7 +5636,7 @@ var findTargetFolder = (nodes, slug) => {
5588
5636
  return roots.find((node) => node.slug === slug && node.type === "folder");
5589
5637
  };
5590
5638
  var resolveTargetState = async (client, targetFolderSlug) => {
5591
- const [nodes, bases] = await Promise.all([client.nodes.list(), client.bases.list()]);
5639
+ const [nodes, bases] = await Promise.all([client.nodes.list({}), client.bases.list({})]);
5592
5640
  return {
5593
5641
  targetFolder: findTargetFolder(nodes, targetFolderSlug),
5594
5642
  existingBaseSlugs: new Set(bases.map((base) => base.slug))
@@ -5867,7 +5915,7 @@ var renderInstallReport = (targetFolderSlug, result) => {
5867
5915
  }
5868
5916
  return lines.join("\n");
5869
5917
  };
5870
- var runPublish = async (client, nodeSlugOrId, options) => {
5918
+ var runExport = async (client, nodeSlugOrId, options) => {
5871
5919
  const nodes = await client.nodes.list();
5872
5920
  const source = findSourceNode(nodes, nodeSlugOrId);
5873
5921
  const warnings = [];
@@ -5884,14 +5932,14 @@ var runPublish = async (client, nodeSlugOrId, options) => {
5884
5932
  }
5885
5933
  if (options.json) {
5886
5934
  return {
5887
- published: !options.dryRun,
5935
+ exported: !options.dryRun,
5888
5936
  package: manifest.name,
5889
5937
  outDir: options.outDir,
5890
5938
  files: [...files.keys()].sort(),
5891
5939
  warnings
5892
5940
  };
5893
5941
  }
5894
- return renderPublishReport(options, manifest, files, warnings);
5942
+ return renderExportReport(options, manifest, files, warnings);
5895
5943
  };
5896
5944
  var resolveManifest = async (outDir, nameFlag, nodeName, nodeSlug) => {
5897
5945
  const existing = await readExistingManifest(outDir);
@@ -5913,7 +5961,7 @@ var readExistingManifest = async (outDir) => {
5913
5961
  return void 0;
5914
5962
  }
5915
5963
  };
5916
- var renderPublishReport = (options, manifest, files, warnings) => {
5964
+ var renderExportReport = (options, manifest, files, warnings) => {
5917
5965
  const lines = [];
5918
5966
  lines.push(
5919
5967
  options.dryRun ? `Dry run \u2014 would write ${files.size} file(s) to ${options.outDir}:` : `Wrote ${files.size} file(s) to ${options.outDir}:`
@@ -5926,7 +5974,7 @@ var renderPublishReport = (options, manifest, files, warnings) => {
5926
5974
  }
5927
5975
  if (!options.dryRun) {
5928
5976
  lines.push("");
5929
- lines.push("Now publish it to GitHub:");
5977
+ lines.push("Now push it to GitHub:");
5930
5978
  lines.push(` cd ${options.outDir}`);
5931
5979
  lines.push(
5932
5980
  ' git init && git add . && git commit -m "Add ${name} package"'.replace(
@@ -6381,10 +6429,17 @@ function classifyKind(inner) {
6381
6429
  function inputFields(inputSchema) {
6382
6430
  if (inputSchema === void 0) return [];
6383
6431
  const { inner } = unwrapSchema(inputSchema);
6384
- const shape = inner?.shape ?? (inner?.def?.type === "object" ? inner.def.shape : void 0);
6385
- if (!shape || typeof shape !== "object") {
6432
+ const shape = objectShape(inner);
6433
+ if (!shape) {
6386
6434
  return [{ key: "input", kind: "json", required: true, root: true }];
6387
6435
  }
6436
+ return fieldsFromShape(shape);
6437
+ }
6438
+ function objectShape(inner) {
6439
+ const shape = inner?.shape ?? (inner?.def?.type === "object" ? inner.def.shape : void 0);
6440
+ return shape && typeof shape === "object" ? shape : void 0;
6441
+ }
6442
+ function fieldsFromShape(shape) {
6388
6443
  const fields = [];
6389
6444
  for (const [key, sub] of Object.entries(shape)) {
6390
6445
  const { inner: unwrapped, optional } = unwrapSchema(sub);
@@ -6393,6 +6448,34 @@ function inputFields(inputSchema) {
6393
6448
  }
6394
6449
  return fields;
6395
6450
  }
6451
+ function unionVariants(inputSchema, discriminator = "operation") {
6452
+ if (inputSchema === void 0) return null;
6453
+ const { inner } = unwrapSchema(inputSchema);
6454
+ let unionNode = inner;
6455
+ let extraShape = {};
6456
+ if (inner?.def?.type === "intersection") {
6457
+ for (const side of [inner.def.left, inner.def.right]) {
6458
+ const { inner: sideInner } = unwrapSchema(side);
6459
+ const shape = objectShape(sideInner);
6460
+ if (shape) extraShape = { ...extraShape, ...shape };
6461
+ else unionNode = sideInner;
6462
+ }
6463
+ }
6464
+ const options = unionNode?.def?.options ?? unionNode?.options;
6465
+ if (!Array.isArray(options) || options.length === 0) return null;
6466
+ const variants = [];
6467
+ for (const option of options) {
6468
+ const { inner: optionInner } = unwrapSchema(option);
6469
+ const shape = objectShape(optionInner);
6470
+ if (!shape) return null;
6471
+ const { inner: discInner } = unwrapSchema(shape[discriminator]);
6472
+ const value = discInner?.def?.values?.[0] ?? discInner?.def?.value;
6473
+ if (typeof value !== "string") return null;
6474
+ const { [discriminator]: _pinned, ...rest } = shape;
6475
+ variants.push({ value, fields: fieldsFromShape({ ...rest, ...extraShape }) });
6476
+ }
6477
+ return variants;
6478
+ }
6396
6479
  var GENERATED_SKIP = /* @__PURE__ */ new Set([
6397
6480
  "search",
6398
6481
  // top-level `search`
@@ -6402,18 +6485,48 @@ var GENERATED_SKIP = /* @__PURE__ */ new Set([
6402
6485
  // `records by-field-text`
6403
6486
  "records.listChangeRequests",
6404
6487
  // `records change-requests`
6405
- "airapps.listFiles"
6406
- // `airapps files`
6488
+ // The `/file-trees` group is exposed by the task layer as `nodes
6489
+ // list-file-trees` / `get-file-tree` / `files` / `read-file` /
6490
+ // `files-change-request`, each taking `--kind` (see tasks/file-tree.ts).
6491
+ "fileTrees.list",
6492
+ "fileTrees.create",
6493
+ "fileTrees.get",
6494
+ "fileTrees.listFiles",
6495
+ "fileTrees.readFile",
6496
+ "fileTrees.createChangeRequest"
6407
6497
  ]);
6408
6498
  function registerGeneratedCommands(program, state) {
6409
6499
  const getGroup = (displayName, tag) => program.commands.find((c2) => c2.name() === displayName) ?? program.command(displayName).description(tag ?? `${displayName} endpoints`);
6410
6500
  const addLeaf = (parent, navPath, procKey, proc) => {
6411
6501
  const { route, inputSchema } = proc["~orpc"];
6412
- const name = [...navPath.slice(1), procKey].map(kebab).join("-");
6502
+ const baseName = [...navPath.slice(1), procKey].map(kebab).join("-");
6503
+ const variants = unionVariants(inputSchema);
6504
+ if (variants) {
6505
+ for (const variant of variants) {
6506
+ addOneLeaf(
6507
+ parent,
6508
+ navPath,
6509
+ procKey,
6510
+ proc,
6511
+ `${kebab(variant.value)}-${baseName}`,
6512
+ [
6513
+ { key: "operation", kind: "string", required: true, pinned: variant.value },
6514
+ ...variant.fields
6515
+ ],
6516
+ // The shared summary describes the merged endpoint; say which branch this is.
6517
+ `${route.summary ?? baseName} \u2014 operation "${variant.value}"`
6518
+ );
6519
+ }
6520
+ return;
6521
+ }
6522
+ addOneLeaf(parent, navPath, procKey, proc, baseName, inputFields(inputSchema));
6523
+ };
6524
+ const addOneLeaf = (parent, navPath, procKey, proc, name, fields, description) => {
6525
+ const { route } = proc["~orpc"];
6413
6526
  if (parent.commands.some((c2) => c2.name() === name || c2.aliases().includes(name))) return;
6414
- const fields = inputFields(inputSchema);
6415
- const leaf = parent.command(name).description(route.summary ?? `${route.method} ${route.path}`);
6527
+ const leaf = parent.command(name).description(description ?? route.summary ?? `${route.method} ${route.path}`);
6416
6528
  for (const f of fields) {
6529
+ if (f.pinned !== void 0) continue;
6417
6530
  const flag = `--${kebab(f.key)}`;
6418
6531
  if (GLOBAL_LONG_FLAGS.has(flag)) continue;
6419
6532
  if (f.kind === "boolean") {
@@ -6446,6 +6559,10 @@ OpenAPI: ${route.method} ${route.path}`);
6446
6559
  runAction(state, (client, opts) => {
6447
6560
  let input = {};
6448
6561
  for (const f of fields) {
6562
+ if (f.pinned !== void 0) {
6563
+ input[f.key] = f.pinned;
6564
+ continue;
6565
+ }
6449
6566
  if (GLOBAL_LONG_FLAGS.has(`--${kebab(f.key)}`)) continue;
6450
6567
  const optKey = f.kind === "json" ? `${f.key}Json` : f.key;
6451
6568
  const value = opts[optKey];
@@ -6677,11 +6794,21 @@ Examples:
6677
6794
  )
6678
6795
  );
6679
6796
  const bases = program.command("bases").description("Bases (structured tables)");
6680
- addGlobalFlags(bases.command("list")).description("List Bases in the active space").action(runAction(state, (client) => client.bases.list()));
6797
+ addGlobalFlags(bases.command("list")).description("List Bases in the active space").addOption(
6798
+ new Option2("--status <active|archived>", "live Bases (default) or archived ones").choices([
6799
+ "active",
6800
+ "archived"
6801
+ ])
6802
+ ).action(
6803
+ runAction(
6804
+ state,
6805
+ (client, opts) => client.bases.list({ status: opts.status ?? "active" })
6806
+ )
6807
+ );
6681
6808
  addGlobalFlags(bases.command("get")).description("Get one Base by slug").requiredOption("--slug <slug>", "Base slug").action(
6682
6809
  runAction(state, async (client, opts) => {
6683
6810
  const slug = opts.slug;
6684
- const found = (await client.bases.list()).find((base) => base.slug === slug);
6811
+ const found = (await client.bases.list({})).find((base) => base.slug === slug);
6685
6812
  if (!found) throw new Error(`no Base with slug "${slug}"`);
6686
6813
  return found;
6687
6814
  })
@@ -6728,7 +6855,8 @@ Examples:
6728
6855
  "No field patch supplied. Pass --name, --required, --options-json, or attachment option flags."
6729
6856
  );
6730
6857
  }
6731
- return client.bases.updateFieldChangeRequest({
6858
+ return client.bases.fieldChangeRequest({
6859
+ operation: "update",
6732
6860
  baseId: opts.baseId,
6733
6861
  fieldId: opts.fieldId,
6734
6862
  patch,
@@ -6767,6 +6895,16 @@ Examples:
6767
6895
  addGlobalFlags(records.command("get")).description("Get one record").requiredOption("--record-id <id>", "record id").action(
6768
6896
  runAction(state, (client, opts) => client.records.get({ recordId: opts.recordId }))
6769
6897
  );
6898
+ addGlobalFlags(records.command("get-by-field")).description("Get one record by an exact field value").requiredOption("--base-id <id>", "Base id").requiredOption("--field-slug <slug>", "field slug").requiredOption("--value-text <value>", "exact field value").action(
6899
+ runAction(
6900
+ state,
6901
+ (client, opts) => getRecordByField(client, {
6902
+ baseId: opts.baseId,
6903
+ fieldSlug: opts.fieldSlug,
6904
+ valueText: opts.valueText
6905
+ })
6906
+ )
6907
+ );
6770
6908
  addGlobalFlags(records.command("change-requests")).description("Change Requests for a record").requiredOption("--record-id <id>", "record id").action(
6771
6909
  runAction(
6772
6910
  state,
@@ -6855,7 +6993,7 @@ Examples:
6855
6993
  addGlobalFlags(assetsCommand.command("grep")).description("Search every text-bearing asset in scope").requiredOption("--pattern <regex>", "literal or regex pattern").option("--flags <flags>", 'RegExp flags, e.g. "i" for case-insensitive').option("--asset-ids <ids...>", "scope: specific asset ids").option("--drive-path <path>", "scope: Drive/Skill mounted path prefix").option("--mime-types <types...>", "scope: MIME types").option("--max-matches <n>", "max matches (default 100, cap 1000)", parsePositiveInt).option("--context-lines <n>", "lines of before/after context (default 0, cap 10)", parseNum).action(
6856
6994
  runAction(
6857
6995
  state,
6858
- (client, opts) => client.assets.grep({
6996
+ (client, opts) => grepAssets(client, {
6859
6997
  pattern: opts.pattern,
6860
6998
  flags: opts.flags,
6861
6999
  scope: opts.assetIds || opts.drivePath || opts.mimeTypes ? {
@@ -6917,19 +7055,19 @@ field with nothing linked yet does not trigger this.`
6917
7055
  })
6918
7056
  )
6919
7057
  );
6920
- addGlobalFlags(program.command("publish")).description("Export a node subtree as a Busabase package directory you can push to GitHub").argument("<node-slug-or-id>", "the folder/node to publish").requiredOption("-o, --out-dir <dir>", "output directory for the package").option("--name <name>", "package name (default: reuse busabase.json, else the node slug)").option("--dry-run", "list the files that would be written and write nothing").addHelpText(
7058
+ addGlobalFlags(program.command("export")).description("Export a node subtree as a Busabase package directory you can push to GitHub").argument("<node-slug-or-id>", "the folder/node to export").requiredOption("-o, --out-dir <dir>", "output directory for the package").option("--name <name>", "package name (default: reuse busabase.json, else the node slug)").option("--dry-run", "list the files that would be written and write nothing").addHelpText(
6921
7059
  "after",
6922
7060
  `
6923
- Output is deterministic: publishing twice from an unchanged space produces
7061
+ Output is deterministic: exporting twice from an unchanged space produces
6924
7062
  byte-identical files, so a GitHub diff shows exactly what changed.
6925
7063
 
6926
7064
  Examples:
6927
- busabase-cli publish support-kb -o ./support-kb-template
6928
- busabase-cli publish support-kb -o ./support-kb-template --name "Support KB" --dry-run`
7065
+ busabase-cli export support-kb -o ./support-kb-template
7066
+ busabase-cli export support-kb -o ./support-kb-template --name "Support KB" --dry-run`
6929
7067
  ).action(
6930
7068
  runArgAction(
6931
7069
  state,
6932
- (nodeSlugOrId, client, opts, config) => runPublish(client, nodeSlugOrId, {
7070
+ (nodeSlugOrId, client, opts, config) => runExport(client, nodeSlugOrId, {
6933
7071
  outDir: opts.outDir,
6934
7072
  name: opts.name,
6935
7073
  dryRun: Boolean(opts.dryRun),
@@ -6941,7 +7079,7 @@ Examples:
6941
7079
  addGlobalFlags(program.command("backup")).description("Back up the active space to a full-fidelity .bbdump archive").option("-o, --out-file <file>", "output archive path", "space.bbdump").option(
6942
7080
  "--no-history",
6943
7081
  "full-fidelity rows, but omit the history tables (commits, change requests, operations, comments, reviews, audit events)"
6944
- ).option("--state-only", "superseded by `busabase-cli publish` \u2014 see the error text").addHelpText(
7082
+ ).option("--state-only", "superseded by `busabase-cli export` \u2014 see the error text").addHelpText(
6945
7083
  "after",
6946
7084
  `
6947
7085
  A backup is full fidelity: raw rows with their original ids, every doc body, and
@@ -6953,7 +7091,7 @@ Examples:
6953
7091
  busabase-cli backup -o ./backups/acme-2026-07-19.bbdump
6954
7092
  busabase-cli backup --no-history # smaller: current state only
6955
7093
 
6956
- To hand content to someone else rather than back it up, use \`busabase-cli publish\`,
7094
+ To hand content to someone else rather than back it up, use \`busabase-cli export\`,
6957
7095
  which writes a readable, diffable package directory you can push to GitHub.`
6958
7096
  ).action(
6959
7097
  runAction(
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-NVBCCPMO.js";
4
+ } from "./chunk-MLTXEKPL.js";
5
5
 
6
6
  // src/cli.ts
7
7
  process.exit(await runCli(process.argv.slice(2)));
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  HELP,
3
3
  render,
4
4
  runCli
5
- } from "./chunk-NVBCCPMO.js";
5
+ } from "./chunk-MLTXEKPL.js";
6
6
 
7
7
  // src/index.ts
8
8
  import {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busa-cli",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "Command-line client for the Busabase OpenAPI REST API. Talks to a local or remote `busabase server`. Short-name alias for busabase-cli.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/busabase/busabase/tree/main/apps/busabase-cli",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@zip.js/zip.js": "^2.8.20",
35
35
  "busabase-package": "workspace:*",
36
- "busabase-sdk": "0.10.1",
36
+ "busabase-sdk": "0.10.3",
37
37
  "commander": "^14.0.0",
38
38
  "tar-stream": "^3.1.7",
39
39
  "yaml": "^2.9.0",