busa-cli 0.10.3 → 0.11.0

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.
@@ -284,6 +284,10 @@ var baseFieldChangeTask = {
284
284
 
285
285
  // ../../packages/busabase-contract/src/tasks/change-request.ts
286
286
  var REVIEW_GUIDANCE = "This records a HUMAN decision. Never call it unless the user explicitly asked for this specific verdict on these specific change requests. Summarising a change request for the user is not permission to approve it.";
287
+ var singleItemError = (result) => Object.assign(new Error(result?.error ?? "Change request action returned no result"), {
288
+ ...result?.code ? { code: result.code } : {},
289
+ ...result?.data === void 0 ? {} : { data: result.data }
290
+ });
287
291
  var changeRequestQueryTask = {
288
292
  name: "change_request_query",
289
293
  cliPath: ["change-requests", "query"],
@@ -363,18 +367,15 @@ var changeRequestReviewTask = {
363
367
  throw new Error("changeRequestIds must contain at least one id.");
364
368
  }
365
369
  const verdict = input.verdict;
366
- if (ids.length === 1) {
367
- return client.changeRequests.review({
368
- changeRequestId: ids[0],
369
- verdict,
370
- ...input.reason ? { reason: input.reason } : {}
371
- });
372
- }
373
- return client.changeRequests.reviewMany({
370
+ const response = await client.changeRequests.review({
374
371
  changeRequestIds: ids,
375
372
  verdict,
376
373
  ...input.reason ? { reason: input.reason } : {}
377
374
  });
375
+ if (ids.length !== 1) return response;
376
+ const result = response.results[0];
377
+ if (!result?.ok) throw singleItemError(result);
378
+ return result.changeRequest;
378
379
  }
379
380
  };
380
381
  var changeRequestMergeTask = {
@@ -401,8 +402,15 @@ var changeRequestMergeTask = {
401
402
  if (!Array.isArray(ids) || ids.length === 0) {
402
403
  throw new Error("changeRequestIds must contain at least one id.");
403
404
  }
404
- if (ids.length === 1) return client.changeRequests.merge({ changeRequestId: ids[0] });
405
- return client.changeRequests.mergeMany({ changeRequestIds: ids });
405
+ const response = await client.changeRequests.merge({ changeRequestIds: ids });
406
+ if (ids.length !== 1) return response;
407
+ const result = response.results[0];
408
+ if (!result?.ok) throw singleItemError(result);
409
+ return {
410
+ changeRequest: result.changeRequest,
411
+ record: result.record,
412
+ view: result.view
413
+ };
406
414
  }
407
415
  };
408
416
 
@@ -434,25 +442,25 @@ var nodeListTask = {
434
442
  {
435
443
  path: ["skills", "list"],
436
444
  preset: { kind: "skill" },
437
- summary: "List Skill nodes with their file trees"
445
+ summary: "List Skill nodes (summaries; use `skills get` for one node's files)"
438
446
  },
439
447
  {
440
448
  path: ["drives", "list"],
441
449
  preset: { kind: "drive" },
442
- summary: "List Drive nodes with their file trees"
450
+ summary: "List Drive nodes (summaries; use `drives get` for one node's files)"
443
451
  },
444
452
  {
445
453
  path: ["airapps", "list"],
446
454
  preset: { kind: "airapp" },
447
- summary: "List AirApp nodes with their file trees"
455
+ summary: "List AirApp nodes (summaries; use `airapps get` for one node's files)"
448
456
  }
449
457
  ],
450
- summary: "List Skill, Drive, or AirApp nodes with their file trees",
451
- guidance: "Returns every node of the given kind together with its Asset-backed file list. For the workspace tree across all node types, use `nodes_list` instead.",
458
+ summary: "List Skill, Drive, or AirApp nodes",
459
+ guidance: "Returns a lightweight summary row for every node of the given kind \u2014 id, name, slug, type, and metadata, but NOT the node's file list. Follow up with `node_get_file_tree` for one node's files, or `node_files_list` for just the file inventory. For the workspace tree across all node types, use `nodes_list` instead.",
452
460
  annotations: { readOnly: true, destructive: false },
453
461
  params: [kindParam],
454
462
  examples: ["busabase-cli nodes list-file-trees --kind skill"],
455
- execute: async (client, input) => client.fileTrees.list({ type: resolveKind(input.kind) })
463
+ execute: async (client, input) => client.nodes.list({ types: [resolveKind(input.kind)] })
456
464
  };
457
465
  var nodeGetTask = {
458
466
  name: "node_get_file_tree",
@@ -478,7 +486,10 @@ var nodeGetTask = {
478
486
  annotations: { readOnly: true, destructive: false },
479
487
  params: [kindParam, nodeIdParam],
480
488
  examples: ["busabase-cli nodes get-file-tree --kind airapp --node-id nod_123"],
481
- execute: async (client, input) => client.fileTrees.get({ nodeId: input.nodeId, type: resolveKind(input.kind) })
489
+ // Reads through the unified Node detail route. The payload is unchanged apart
490
+ // from the `type` discriminator the union carries, so a caller still gets
491
+ // `{ node, entryFile, visibility, version, files }`.
492
+ execute: async (client, input) => client.nodes.get({ nodeId: input.nodeId, type: resolveKind(input.kind) })
482
493
  };
483
494
  var nodeListFilesTask = {
484
495
  name: "node_files_list",
@@ -567,7 +578,7 @@ var nodeFilesChangeRequestTask = {
567
578
  }
568
579
  ],
569
580
  summary: "Propose file changes inside a Skill, Drive, or AirApp node",
570
- guidance: "Approval-first: this proposes a ChangeRequest for a human to review, it does not write directly. Each operation is one of create / update / delete / metadata_update. Include `baseContentHash` (from `node_file_read`) on an update so a concurrent edit is caught.",
581
+ guidance: 'Approval-first: this proposes a ChangeRequest for a human to review, it does not write directly. Each operation is one of create / update / delete / metadata_update. Include `baseContentHash` (from `node_file_read`) on an update so a concurrent edit is caught. For kind "airapp", keep the project runnable by `npm run dev` \u2014 editing package.json must leave a "dev" script starting a plain Node server, not a bundler dev server.',
571
582
  annotations: { readOnly: false, destructive: false },
572
583
  params: [
573
584
  kindParam,
@@ -1189,7 +1200,7 @@ var nodeCreateTask = {
1189
1200
  name: "files",
1190
1201
  appliesWhen: { param: "type", values: ["skill", "drive", "airapp"] },
1191
1202
  kind: "json",
1192
- description: 'Seed files for a Skill/Drive/AirApp: [{"path":"SKILL.md","content":"..."}]. Layered over the default scaffold unless mergeMode is "replace".'
1203
+ description: 'Seed files for a Skill/Drive/AirApp: [{"path":"SKILL.md","content":"..."}]. Layered over the default scaffold unless mergeMode is "replace" \u2014 a path you supply REPLACES the scaffold\'s file at that path. For --type airapp the project MUST be runnable by `npm run dev`: give package.json a "dev" script running a plain Node server (`node server.js`, Hono or node:http). A bundler dev server (Vite/webpack/Next) and any native-binary dependency CANNOT boot in the AirApp runtime, and browser files must need no build step.'
1193
1204
  },
1194
1205
  {
1195
1206
  name: "mergeMode",
@@ -1433,11 +1444,16 @@ var recordQueryTask = {
1433
1444
 
1434
1445
  // ../../packages/busabase-contract/src/tasks/view-change.ts
1435
1446
  var ACTIONS = ["create", "update", "delete", "restore"];
1447
+ var mergeIntent2 = (input) => {
1448
+ if (input.requireReview) return { autoMerge: false };
1449
+ if (input.autoMerge) return { autoMerge: true };
1450
+ return {};
1451
+ };
1436
1452
  var viewChangeTask = {
1437
1453
  name: "view_change_request",
1438
1454
  cliPath: ["views", "change-request"],
1439
1455
  summary: "Propose a Base view change (create / update / delete / restore)",
1440
- guidance: "Approval-first: proposes a ChangeRequest, it does not change the view directly. `create` needs the BASE id; update / delete / restore need the VIEW id \u2014 the endpoint split follows whichever id identifies the target, which this task handles for you.",
1456
+ guidance: "Review is permission-aware, decided server-side: the change merges immediately when your key has write access on the Base's node and lands as a pending ChangeRequest otherwise \u2014 check the response's `materialized` field to see which happened. Pass requireReview to always propose instead of merging. Views are structure, not content \u2014 when you are building starter structure (a Base plus the views that make it usable) let it merge so the workspace is not left half-built behind a review queue, exactly as you would for `node_create` and `POST /bases`. `create` needs the BASE id; update / delete / restore need the VIEW id \u2014 the endpoint split follows whichever id identifies the target, which this task handles for you.",
1441
1457
  annotations: { readOnly: false, destructive: false },
1442
1458
  params: [
1443
1459
  {
@@ -1484,16 +1500,30 @@ var viewChangeTask = {
1484
1500
  description: "Changes to apply to the view (update only)."
1485
1501
  },
1486
1502
  { name: "message", kind: "string", description: "Explanation for the reviewer." },
1487
- { name: "submittedBy", kind: "string", description: "Producer label recorded on the change." }
1503
+ { name: "submittedBy", kind: "string", description: "Producer label recorded on the change." },
1504
+ {
1505
+ name: "autoMerge",
1506
+ kind: "boolean",
1507
+ description: "Skip review and apply the change immediately if you have write access. Not a permission override \u2014 a changeRequest-level key still gets a pending CR. Default is permission-aware: merge when you can, otherwise propose."
1508
+ },
1509
+ {
1510
+ name: "requireReview",
1511
+ kind: "boolean",
1512
+ description: "Always propose a pending ChangeRequest, even with write access."
1513
+ }
1488
1514
  ],
1489
1515
  examples: [
1490
1516
  'busabase-cli views change-request --action create --base-id bas_1 --name "Published" --type table',
1517
+ 'busabase-cli views change-request --action create --base-id bas_1 --name "Published" --require-review',
1491
1518
  "busabase-cli views change-request --action delete --view-id viw_1"
1492
1519
  ],
1493
1520
  execute: async (client, input) => {
1494
1521
  const meta = {
1495
1522
  ...input.message ? { message: input.message } : {},
1496
- ...input.submittedBy ? { submittedBy: input.submittedBy } : {}
1523
+ ...input.submittedBy ? { submittedBy: input.submittedBy } : {},
1524
+ // Tri-state, like node_create's: omitted keeps the endpoint's
1525
+ // permission-aware default, explicit true/false forces the branch.
1526
+ ...mergeIntent2(input)
1497
1527
  };
1498
1528
  if (input.action === "create") {
1499
1529
  if (!input.baseId || !input.name) {
@@ -2243,8 +2273,11 @@ async function exportFull(options) {
2243
2273
  let docBodyCount = 0;
2244
2274
  for (const node of docNodes) {
2245
2275
  const nodeId = node.id;
2246
- const doc = await client.docs.get({ nodeId });
2247
- await writer.addBuffer(`docs/${nodeId}.md`, doc.body ?? "");
2276
+ const detail = await client.nodes.get({ nodeId, type: "doc" });
2277
+ if (detail.type !== "doc") {
2278
+ throw new Error(`Expected a doc node for ${nodeId}, got "${detail.type}"`);
2279
+ }
2280
+ await writer.addBuffer(`docs/${nodeId}.md`, detail.body ?? "");
2248
2281
  docBodyCount += 1;
2249
2282
  }
2250
2283
  log(`exported ${docBodyCount} doc bodies`);
@@ -3465,7 +3498,33 @@ var listNodesInputSchema = z7.object({
3465
3498
  * soft-archived nodes for the Trash view — no `parentId`/`depth` walk, since
3466
3499
  * archived nodes are shown as a list, not a tree.
3467
3500
  */
3468
- status: z7.enum(["active", "archived"]).optional().default("active")
3501
+ status: z7.enum(["active", "archived"]).optional().default("active"),
3502
+ /**
3503
+ * Narrow to specific node types and return a FLAT list of lightweight node
3504
+ * summaries (`children: []`) instead of walking the tree. This is what
3505
+ * replaced the four retired narrow listings (`GET /docs`, `/files`,
3506
+ * `/folders`, `/file-trees`); file-trees are selected with their real
3507
+ * discriminators `skill` / `drive` / `airapp`, since there is no synthetic
3508
+ * "file-tree" node type.
3509
+ *
3510
+ * Omitting `types` leaves every existing caller on exactly today's
3511
+ * behaviour (full tree, or a `parentId`/`depth`-bounded walk, or the
3512
+ * archived flat list) — the two modes never interfere.
3513
+ *
3514
+ * NOTE — no `projection` parameter, deliberately. The consolidation roadmap
3515
+ * sketched `?projection=summary`, but it also rules out adding
3516
+ * `projection=detail` in this batch (the retired detail lists were the
3517
+ * N+1 payloads this change exists to remove). That would leave a parameter
3518
+ * with exactly one legal value, which is noise in OpenAPI/MCP/CLI rather
3519
+ * than a decision a caller gets to make. Detail is `GET /nodes/{nodeId}`.
3520
+ *
3521
+ * A GET query param that occurs exactly once (`?types=doc`) arrives as a
3522
+ * bare string, not a 1-element array — only a REPEATED occurrence
3523
+ * (`?types=doc&types=file`) becomes an array. Accept both and normalize.
3524
+ */
3525
+ types: z7.union([z7.array(z7.enum(NODE_TYPES)), z7.enum(NODE_TYPES)]).transform((value) => Array.isArray(value) ? value : [value]).optional().describe(
3526
+ "Return a flat list of lightweight summaries for these node types instead of the tree. Read one node's full detail with GET /nodes/{nodeId}."
3527
+ )
3469
3528
  }).optional();
3470
3529
  var isDescendantInputSchema = z7.object({
3471
3530
  nodeId: z7.string(),
@@ -3633,11 +3692,19 @@ var liveEventSchema = z7.object({
3633
3692
  // via the audit funnel, but nothing needs reviewing). Consumed by
3634
3693
  // `use-live-sync.ts` to pop a desktop Notification, and by
3635
3694
  // busabase-cloud's host hook to persist an inbox notification row.
3636
- "change_request.pending_review"
3695
+ "change_request.pending_review",
3696
+ // A node's metadata was written directly, outside the change-request flow
3697
+ // (`PATCH /api/v1/nodes/{nodeId}/metadata` — agents, the SDK, an MCP tool,
3698
+ // and every rich-node editor's own Save). Carries the touched node in
3699
+ // `nodeIds` so open dashboards refetch the node tree instead of showing a
3700
+ // stale whiteboard/workflow/HTML document until the next reload.
3701
+ "node.metadata_updated"
3637
3702
  ]),
3638
3703
  spaceId: z7.string(),
3639
3704
  actorId: z7.string(),
3640
- changeRequestId: z7.string(),
3705
+ // Null for events that aren't about a change request at all
3706
+ // (`node.metadata_updated`), which is every direct, auto-audited write.
3707
+ changeRequestId: z7.string().nullable(),
3641
3708
  baseId: z7.string().nullable(),
3642
3709
  nodeIds: z7.array(z7.string()),
3643
3710
  recordIds: z7.array(z7.string()),
@@ -3948,6 +4015,9 @@ var viewSchema = z8.object({
3948
4015
  createdAt: z8.string(),
3949
4016
  updatedAt: z8.string()
3950
4017
  });
4018
+ var autoMergeSchema = z8.boolean().optional().describe(
4019
+ "Whether to approve and merge this view change immediately. Omitted defaults to merging immediately if the actor has write access on the Base's node, otherwise falling back to a pending Change Request; pass explicit false to force review even with write access."
4020
+ );
3951
4021
  var createViewInputSchema = z8.object({
3952
4022
  config: viewConfigSchema.optional().default({ filters: [], sorts: [] }),
3953
4023
  description: z8.string().optional().default(""),
@@ -3955,7 +4025,8 @@ var createViewInputSchema = z8.object({
3955
4025
  name: z8.string().min(1),
3956
4026
  type: viewTypeSchema.optional().default("table"),
3957
4027
  slug: z8.string().min(1).regex(/^[a-z0-9-]+$/).optional(),
3958
- submittedBy: z8.string().optional().default("local-producer")
4028
+ submittedBy: z8.string().optional().default("local-producer"),
4029
+ autoMerge: autoMergeSchema
3959
4030
  });
3960
4031
  var updateViewInputSchema = z8.object({
3961
4032
  config: viewConfigSchema.optional(),
@@ -3963,15 +4034,18 @@ var updateViewInputSchema = z8.object({
3963
4034
  message: z8.string().optional().default("Update view"),
3964
4035
  name: z8.string().min(1).optional(),
3965
4036
  type: viewTypeSchema.optional(),
3966
- submittedBy: z8.string().optional().default("local-producer")
4037
+ submittedBy: z8.string().optional().default("local-producer"),
4038
+ autoMerge: autoMergeSchema
3967
4039
  });
3968
4040
  var deleteViewInputSchema = z8.object({
3969
4041
  message: z8.string().optional().default("Delete view"),
3970
- submittedBy: z8.string().optional().default("local-producer")
4042
+ submittedBy: z8.string().optional().default("local-producer"),
4043
+ autoMerge: autoMergeSchema
3971
4044
  });
3972
4045
  var restoreViewInputSchema = z8.object({
3973
4046
  message: z8.string().optional().default("Restore view"),
3974
- submittedBy: z8.string().optional().default("local-producer")
4047
+ submittedBy: z8.string().optional().default("local-producer"),
4048
+ autoMerge: autoMergeSchema
3975
4049
  });
3976
4050
  var viewChangeRequestInputSchema = z8.discriminatedUnion("operation", [
3977
4051
  createViewInputSchema.extend({
@@ -4486,16 +4560,25 @@ var createBase = async (client, node, parentNodeId, options, state, bases, field
4486
4560
  bases.push({ node, baseId: result.id });
4487
4561
  indexFields(fieldIds, node.slug, result.fields);
4488
4562
  for (const view of node.base.views) {
4489
- const changeRequest = await client.views.changeRequest({
4563
+ const created = await client.views.changeRequest({
4490
4564
  operation: "create",
4491
4565
  baseId: result.id,
4492
4566
  slug: view.slug,
4493
4567
  name: view.name,
4494
4568
  description: view.description,
4495
4569
  config: view.config,
4496
- submittedBy: options.submittedBy
4570
+ submittedBy: options.submittedBy,
4571
+ // Structure, exactly like the Base above — so ask for it outright instead
4572
+ // of proposing it and immediately approving+merging our own proposal,
4573
+ // which is what this loop used to do only because the endpoint had no
4574
+ // `autoMerge`.
4575
+ autoMerge: true
4497
4576
  });
4498
- await approveAndMerge(client, changeRequest.id);
4577
+ if (!created.materialized) {
4578
+ throw new Error(
4579
+ `View "${view.slug}" was requested immediately but the server returned a pending change request; install cannot leave a Base's views behind in review.`
4580
+ );
4581
+ }
4499
4582
  state.created.views++;
4500
4583
  }
4501
4584
  };
@@ -4718,8 +4801,32 @@ var toKeyArray = (value) => {
4718
4801
  return [];
4719
4802
  };
4720
4803
  var approveAndMerge = async (client, changeRequestId) => {
4721
- await client.changeRequests.review({ changeRequestId, verdict: "approved" });
4722
- return client.changeRequests.merge({ changeRequestId });
4804
+ const reviewed = await client.changeRequests.review({
4805
+ changeRequestIds: [changeRequestId],
4806
+ verdict: "approved"
4807
+ });
4808
+ const reviewResult = reviewed.results[0];
4809
+ if (!reviewResult?.ok) {
4810
+ throw Object.assign(
4811
+ new Error(reviewResult?.error ?? "Change request review returned no result"),
4812
+ {
4813
+ code: reviewResult?.code,
4814
+ data: reviewResult?.data
4815
+ }
4816
+ );
4817
+ }
4818
+ const merged = await client.changeRequests.merge({ changeRequestIds: [changeRequestId] });
4819
+ const mergeResult = merged.results[0];
4820
+ if (!mergeResult?.ok) {
4821
+ throw Object.assign(
4822
+ new Error(mergeResult?.error ?? "Change request merge returned no result"),
4823
+ {
4824
+ code: mergeResult?.code,
4825
+ data: mergeResult?.data
4826
+ }
4827
+ );
4828
+ }
4829
+ return mergeResult;
4723
4830
  };
4724
4831
 
4725
4832
  // ../../packages/busabase-package/src/collect.ts
@@ -4766,7 +4873,10 @@ var collectNode = async (client, source, options) => {
4766
4873
  children: await collectNodes(client, source.children ?? [], options)
4767
4874
  };
4768
4875
  case "doc": {
4769
- const doc = await client.docs.get({ nodeId: source.id });
4876
+ const doc = await client.nodes.get({ nodeId: source.id, type: "doc" });
4877
+ if (doc.type !== "doc") {
4878
+ throw new Error(`Expected a doc node for "${source.slug}", got "${doc.type}".`);
4879
+ }
4770
4880
  return { ...common, type: "doc", body: doc.body };
4771
4881
  }
4772
4882
  case "base":
@@ -4950,7 +5060,10 @@ var collectFileTree = async (client, source, type, common, options) => {
4950
5060
  };
4951
5061
  };
4952
5062
  var collectFile = async (client, source, common, options) => {
4953
- const fileNode = await client.files.get({ nodeId: source.id });
5063
+ const fileNode = await client.nodes.get({ nodeId: source.id, type: "file" });
5064
+ if (fileNode.type !== "file") {
5065
+ throw new Error(`Expected a file node for "${source.slug}", got "${fileNode.type}".`);
5066
+ }
4954
5067
  const asset = fileNode.asset;
4955
5068
  if (!asset?.url) {
4956
5069
  options.warn(`Skipped file node "${source.slug}": it has no downloadable asset.`);
@@ -6488,12 +6601,17 @@ var GENERATED_SKIP = /* @__PURE__ */ new Set([
6488
6601
  // The `/file-trees` group is exposed by the task layer as `nodes
6489
6602
  // list-file-trees` / `get-file-tree` / `files` / `read-file` /
6490
6603
  // `files-change-request`, each taking `--kind` (see tasks/file-tree.ts).
6491
- "fileTrees.list",
6604
+ // `fileTrees.list`/`fileTrees.get` are absent because the procedures are: the
6605
+ // two tasks that used to call them now call `nodes.list({ types })` /
6606
+ // `nodes.get`, so there is nothing left for the generator to skip.
6492
6607
  "fileTrees.create",
6493
- "fileTrees.get",
6494
6608
  "fileTrees.listFiles",
6495
6609
  "fileTrees.readFile",
6496
6610
  "fileTrees.createChangeRequest"
6611
+ // `nodes get` is the curated `docs|files|folders get` facade's underlying
6612
+ // route, but it stays generated too: it is the ONE command that reads a node
6613
+ // of any type without knowing the type first, which is the whole point of the
6614
+ // consolidation.
6497
6615
  ]);
6498
6616
  function registerGeneratedCommands(program, state) {
6499
6617
  const getGroup = (displayName, tag) => program.commands.find((c2) => c2.name() === displayName) ?? program.command(displayName).description(tag ?? `${displayName} endpoints`);
@@ -7150,6 +7268,44 @@ To add content to a space that is already in use, use \`busabase-cli install\` i
7150
7268
  );
7151
7269
  })
7152
7270
  );
7271
+ for (const facade of [
7272
+ {
7273
+ group: "docs",
7274
+ type: "doc",
7275
+ label: "Doc",
7276
+ detail: "body included",
7277
+ omitted: "no bodies"
7278
+ },
7279
+ {
7280
+ group: "files",
7281
+ type: "file",
7282
+ label: "File",
7283
+ detail: "backing Asset included",
7284
+ omitted: "no Asset detail"
7285
+ },
7286
+ {
7287
+ group: "folders",
7288
+ type: "folder",
7289
+ label: "Folder",
7290
+ detail: "direct children included",
7291
+ omitted: "no children"
7292
+ }
7293
+ ]) {
7294
+ const group = program.commands.find((c2) => c2.name() === facade.group) ?? program.command(facade.group).description(`${facade.label} nodes`);
7295
+ addGlobalFlags(group.command("list")).description(`List ${facade.label} nodes \u2014 summaries only (${facade.omitted})`).addHelpText(
7296
+ "after",
7297
+ `
7298
+ OpenAPI: GET /nodes?types=${facade.type}
7299
+ Follow up with \`${facade.group} get --node-id <id>\` for one node's full detail.`
7300
+ ).action(runAction(state, (client) => client.nodes.list({ types: [facade.type] })));
7301
+ addGlobalFlags(group.command("get")).description(`Get one ${facade.label} node (${facade.detail})`).requiredOption("--node-id <id>", `${facade.label} node id, or a slug unique to this type`).addHelpText("after", `
7302
+ OpenAPI: GET /nodes/{nodeId}?type=${facade.type}`).action(
7303
+ runAction(
7304
+ state,
7305
+ (client, opts) => client.nodes.get({ nodeId: opts.nodeId, type: facade.type })
7306
+ )
7307
+ );
7308
+ }
7153
7309
  registerTaskCommands(program, BUSABASE_TASKS, {
7154
7310
  addGlobalFlags,
7155
7311
  parseFieldSpecs,
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  runCli
4
- } from "./chunk-MLTXEKPL.js";
4
+ } from "./chunk-QJJMAA3K.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-MLTXEKPL.js";
5
+ } from "./chunk-QJJMAA3K.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.3",
3
+ "version": "0.11.0",
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.3",
36
+ "busabase-sdk": "0.11.0",
37
37
  "commander": "^14.0.0",
38
38
  "tar-stream": "^3.1.7",
39
39
  "yaml": "^2.9.0",