busa-cli 0.51.0 → 0.52.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.
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as runCli, r as pkgVersion, t as checkForUpdate } from "./update-check-BWyS6qOd.js";
2
+ import { i as runCli, r as pkgVersion, t as checkForUpdate } from "./update-check-CNYcPsfm.js";
3
3
  //#region src/cli.ts
4
4
  const exitCode = await runCli(process.argv.slice(2));
5
5
  const notice = await checkForUpdate("busabase-cli", pkgVersion());
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { a as render, i as runCli, n as HELP, r as pkgVersion, t as checkForUpdate } from "./update-check-BWyS6qOd.js";
1
+ import { a as render, i as runCli, n as HELP, r as pkgVersion, t as checkForUpdate } from "./update-check-CNYcPsfm.js";
2
2
  import { DEFAULT_BASE_URL, createBusabaseClient, normalizeBaseUrl } from "busabase-sdk";
3
3
  export { DEFAULT_BASE_URL, HELP, checkForUpdate, createBusabaseClient, normalizeBaseUrl, pkgVersion, render, runCli };
@@ -1621,6 +1621,90 @@ const rejectMismatchedPayload = (input) => {
1621
1621
  if ((input.files !== void 0 || input.mergeMode !== void 0) && !FILE_TREE_TYPES.has(type)) wrong.push(input.files !== void 0 ? "files" : "mergeMode");
1622
1622
  if (wrong.length > 0) throw new Error(`${wrong.join(", ")} ${wrong.length > 1 ? "are" : "is"} not valid for --type ${type}.`);
1623
1623
  };
1624
+ /**
1625
+ * Validate `agentPrompts` BEFORE the node is created, against the same schema
1626
+ * the dedicated endpoint and `nodes set-agent-prompts` use.
1627
+ *
1628
+ * Order matters: prompts are written in a second call, so a malformed list
1629
+ * discovered afterwards would leave a created node behind and still fail. The
1630
+ * caller then has a node they did not expect and an error that reads like the
1631
+ * creation failed. Validating first makes the whole task all-or-nothing for the
1632
+ * one failure mode the caller can actually fix.
1633
+ */
1634
+ const parseAgentPrompts = (input) => {
1635
+ if (input.agentPrompts === void 0 || input.agentPrompts === null) return void 0;
1636
+ const parsed = customAgentPromptsSchema.safeParse(input.agentPrompts);
1637
+ if (!parsed.success) {
1638
+ const detail = parsed.error.issues.map((issue) => `${issue.path.length > 0 ? `${issue.path.join(".")}: ` : ""}${issue.message}`).join("; ");
1639
+ throw new Error(`Invalid agentPrompts — ${detail}`);
1640
+ }
1641
+ return parsed.data;
1642
+ };
1643
+ const isRecord = (value) => typeof value === "object" && value !== null;
1644
+ /**
1645
+ * The node id to hang prompts on, or `undefined` when this call did not
1646
+ * materialize a node.
1647
+ *
1648
+ * Every create endpoint returns a `materialized` discriminator, but the
1649
+ * materialized half is shaped per type: a Base VO carries the owning `nodeId`,
1650
+ * a Doc/File/Skill/Drive/AirApp wraps its node under `node`, and the generic
1651
+ * tree endpoint returns a ChangeRequest whose merged create operation names it.
1652
+ */
1653
+ const materializedNodeId = (type, result) => {
1654
+ if (!isRecord(result)) return void 0;
1655
+ if (result.materialized === false) return void 0;
1656
+ if (type === "base") return typeof result.nodeId === "string" ? result.nodeId : void 0;
1657
+ if (isRecord(result.node) && typeof result.node.id === "string") return result.node.id;
1658
+ if (result.status !== "merged") return void 0;
1659
+ const created = (Array.isArray(result.operations) ? result.operations : []).find((op) => isRecord(op) && typeof op.nodeId === "string");
1660
+ return isRecord(created) ? created.nodeId : void 0;
1661
+ };
1662
+ /**
1663
+ * Write the prompts onto the node just created, and report what happened in the
1664
+ * task's own result.
1665
+ *
1666
+ * Two deliberate choices:
1667
+ *
1668
+ * - **A pending ChangeRequest is reported, never silently dropped.** Review-first
1669
+ * callers get a node id only after a human merges, so the prompts genuinely
1670
+ * cannot be written here. Saying so (with the command that finishes the job)
1671
+ * is the difference between "not yet" and a caller believing prompts exist.
1672
+ * - **A server without the dedicated endpoint falls back to the metadata key.**
1673
+ * The CLI and the server ship separately, so a current caller routinely talks
1674
+ * to an older server; `nodes set-agent-prompts` already carries this fallback
1675
+ * for exactly that reason and this path must not be the one that regresses.
1676
+ */
1677
+ const writeAgentPrompts = async (client, type, result, agentPrompts) => {
1678
+ const nodeId = materializedNodeId(type, result);
1679
+ if (!nodeId) return {
1680
+ ...isRecord(result) ? result : { result },
1681
+ agentPromptsWrite: {
1682
+ written: false,
1683
+ reason: "Prompts are addressed by node id, and this call proposed the node for review instead of creating it — there is no node id yet.",
1684
+ nextStep: "Once the change request merges: busabase-cli nodes set-agent-prompts --node-id <nodeId> --file prompts.json"
1685
+ }
1686
+ };
1687
+ try {
1688
+ await client.nodes.updateAgentPrompts({
1689
+ nodeId,
1690
+ agentPrompts
1691
+ });
1692
+ } catch (error) {
1693
+ if ((isRecord(error) ? error.status : void 0) !== 404) throw error;
1694
+ await client.nodes.updateMetadata({
1695
+ nodeId,
1696
+ metadata: { agentPrompts }
1697
+ });
1698
+ }
1699
+ return {
1700
+ ...isRecord(result) ? result : { result },
1701
+ agentPromptsWrite: {
1702
+ written: true,
1703
+ nodeId,
1704
+ count: agentPrompts.length
1705
+ }
1706
+ };
1707
+ };
1624
1708
  /** `autoMerge` is tri-state: unset (permission-aware default), forced on, forced off. */
1625
1709
  const mergeIntent$3 = (input) => {
1626
1710
  if (input.requireReview) return { autoMerge: false };
@@ -1635,6 +1719,66 @@ const mergeIntent$3 = (input) => {
1635
1719
  * new field type added.
1636
1720
  */
1637
1721
  const asFieldArray = (value) => Array.isArray(value) ? value : void 0;
1722
+ /**
1723
+ * Dispatch to the endpoint that can actually build this node type. Split out
1724
+ * of `execute` so the prompt write that follows reads as its own step.
1725
+ */
1726
+ const createNode = async (client, input) => {
1727
+ const type = input.type;
1728
+ const common = {
1729
+ slug: input.slug,
1730
+ name: input.name,
1731
+ description: input.description,
1732
+ parentNodeId: input.parentNodeId,
1733
+ ...mergeIntent$3(input)
1734
+ };
1735
+ if (FILE_TREE_TYPES.has(type)) {
1736
+ const payload = {
1737
+ ...common,
1738
+ type,
1739
+ ...Array.isArray(input.files) ? { files: input.files } : {},
1740
+ ...input.mergeMode ? { mergeMode: input.mergeMode } : {},
1741
+ ...input.visibility ? { visibility: input.visibility } : {},
1742
+ ...input.version ? { version: input.version } : {}
1743
+ };
1744
+ return client.fileTrees.create(payload);
1745
+ }
1746
+ if (type === "doc") {
1747
+ const payload = {
1748
+ ...common,
1749
+ body: input.body ?? ""
1750
+ };
1751
+ return client.docs.create(payload);
1752
+ }
1753
+ if (type === "base") {
1754
+ const payload = {
1755
+ ...common,
1756
+ fields: asFieldArray(input.fields) ?? []
1757
+ };
1758
+ return client.bases.create(payload);
1759
+ }
1760
+ if (type === "file") {
1761
+ if (!input.assetId) throw new Error("assetId is required for type \"file\".");
1762
+ const payload = {
1763
+ ...common,
1764
+ assetId: input.assetId
1765
+ };
1766
+ return client.files.create(payload);
1767
+ }
1768
+ return client.nodes.createChangeRequest({
1769
+ message: input.message ?? `Create ${type} ${input.name}`,
1770
+ submittedBy: input.submittedBy,
1771
+ ...mergeIntent$3(input),
1772
+ operations: [{
1773
+ kind: "create",
1774
+ nodeType: type,
1775
+ slug: input.slug,
1776
+ name: input.name,
1777
+ description: input.description,
1778
+ parentNodeId: input.parentNodeId
1779
+ }]
1780
+ });
1781
+ };
1638
1782
  const nodeCreateTask = {
1639
1783
  name: "node_create",
1640
1784
  cliPath: ["nodes", "create"],
@@ -1672,7 +1816,7 @@ const nodeCreateTask = {
1672
1816
  }
1673
1817
  ],
1674
1818
  summary: "Create a workspace node of any type",
1675
- guidance: "One call creates any of the 11 node types with its type-specific payload: `fields` for a Base, `body` for a Doc, `files` for a Skill/Drive/AirApp, `assetId` for a File. Review is permission-aware, decided server-side: this merges immediately when you already have write access on the parent node, and proposes a ChangeRequest for a human otherwise. Pass requireReview to always propose instead of merging.",
1819
+ guidance: "One call creates any of the 11 node types with its type-specific payload: `fields` for a Base, `body` for a Doc, `files` for a Skill/Drive/AirApp, `assetId` for a File. Review is permission-aware, decided server-side: this merges immediately when you already have write access on the parent node, and proposes a ChangeRequest for a human otherwise. Pass requireReview to always propose instead of merging. Before you finish: if the person will come back to this node to do the same job again, pass agentPrompts so the node opens with THEIR job on it instead of the node type's generic list.",
1676
1820
  annotations: {
1677
1821
  readOnly: false,
1678
1822
  destructive: false
@@ -1818,6 +1962,11 @@ const nodeCreateTask = {
1818
1962
  name: "requireReview",
1819
1963
  kind: "boolean",
1820
1964
  description: "Always propose a pending ChangeRequest, even with write access."
1965
+ },
1966
+ {
1967
+ name: "agentPrompts",
1968
+ kind: "json",
1969
+ description: "Scenario prompts shown when someone opens this node and asks an agent for something: [{\"key\":\"log-visit\",\"label\":\"Log a customer visit\",\"body\":\"{target}\\n\\nAdd a visit record with today's date, the contact I name, and a one-line summary.\",\"intent\":\"change\"}]. Write what the PERSON wants in their own words (\"Log a customer visit\"), not the operation (\"Create a record in Visits\") — the node type already covers the operations. These REPLACE the node type's default scenario prompts, so 2-5 real recurring jobs help and a generic pair is worse than none: omit this when you cannot name one. `{target}` expands to a complete sentence naming the node and space, so give it its own line. Stored in a second call after the node exists, so they are skipped (and reported) when this call proposes a ChangeRequest instead of creating the node; add them afterwards with `nodes set-agent-prompts`."
1821
1970
  }
1822
1971
  ],
1823
1972
  examples: [
@@ -1825,64 +1974,14 @@ const nodeCreateTask = {
1825
1974
  "busabase-cli nodes create --type base --slug blog --name \"Blog Posts\" --field title:Title:text --field body:Body:markdown",
1826
1975
  "busabase-cli nodes create --type doc --slug readme --name \"README\" --body \"# Hello\"",
1827
1976
  "busabase-cli nodes create --type skill --slug my-skill --name \"My Skill\" --files-json @files.json",
1828
- "busabase-cli nodes create --type folder --slug cms --name \"CMS\" --require-review"
1977
+ "busabase-cli nodes create --type folder --slug cms --name \"CMS\" --require-review",
1978
+ "busabase-cli nodes create --type base --slug visits --name \"Visits\" --field title:Title:text --agent-prompts-json @prompts.json"
1829
1979
  ],
1830
1980
  execute: async (client, input) => {
1831
1981
  rejectMismatchedPayload(input);
1832
- const type = input.type;
1833
- const common = {
1834
- slug: input.slug,
1835
- name: input.name,
1836
- description: input.description,
1837
- parentNodeId: input.parentNodeId,
1838
- ...mergeIntent$3(input)
1839
- };
1840
- if (FILE_TREE_TYPES.has(type)) {
1841
- const payload = {
1842
- ...common,
1843
- type,
1844
- ...Array.isArray(input.files) ? { files: input.files } : {},
1845
- ...input.mergeMode ? { mergeMode: input.mergeMode } : {},
1846
- ...input.visibility ? { visibility: input.visibility } : {},
1847
- ...input.version ? { version: input.version } : {}
1848
- };
1849
- return client.fileTrees.create(payload);
1850
- }
1851
- if (type === "doc") {
1852
- const payload = {
1853
- ...common,
1854
- body: input.body ?? ""
1855
- };
1856
- return client.docs.create(payload);
1857
- }
1858
- if (type === "base") {
1859
- const payload = {
1860
- ...common,
1861
- fields: asFieldArray(input.fields) ?? []
1862
- };
1863
- return client.bases.create(payload);
1864
- }
1865
- if (type === "file") {
1866
- if (!input.assetId) throw new Error("assetId is required for type \"file\".");
1867
- const payload = {
1868
- ...common,
1869
- assetId: input.assetId
1870
- };
1871
- return client.files.create(payload);
1872
- }
1873
- return client.nodes.createChangeRequest({
1874
- message: input.message ?? `Create ${type} ${input.name}`,
1875
- submittedBy: input.submittedBy,
1876
- ...mergeIntent$3(input),
1877
- operations: [{
1878
- kind: "create",
1879
- nodeType: type,
1880
- slug: input.slug,
1881
- name: input.name,
1882
- description: input.description,
1883
- parentNodeId: input.parentNodeId
1884
- }]
1885
- });
1982
+ const agentPrompts = parseAgentPrompts(input);
1983
+ const created = await createNode(client, input);
1984
+ return agentPrompts === void 0 ? created : writeAgentPrompts(client, input.type, created, agentPrompts);
1886
1985
  }
1887
1986
  };
1888
1987
  //#endregion
@@ -6983,10 +7082,19 @@ const PackageManifestSchema = z.object({
6983
7082
  * iString: none of those call sites expect a locale-keyed object, and
6984
7083
  * "the directory name is the package's identity, not a label beside it"
6985
7084
  * (audit.ts) is exactly the argument against ever making it one. A
6986
- * human-facing translated title belongs in `description` below, not here.
7085
+ * human-facing translated title belongs in `displayName` below, not here.
6987
7086
  */
6988
7087
  name: z.string().min(1),
6989
7088
  /**
7089
+ * Optional human-facing card title, shown instead of `name` wherever a
7090
+ * template is presented to a reader (never for identity — routing, install
7091
+ * folder, CLI sort, and SKILL.md frontmatter matching all still key off
7092
+ * `name`, untouched by this field). Absent by default: a package written
7093
+ * before this field, or whose author didn't bother, falls back to `name`
7094
+ * and renders exactly as before — this is additive, not a migration.
7095
+ */
7096
+ displayName: iStringSchema.optional(),
7097
+ /**
6990
7098
  * The one-line blurb shown on a template's catalog card, and (via
6991
7099
  * `layout-read.ts`) the description on the Folder node created at install
6992
7100
  * time — Folder/node descriptions are already iString-capable text
@@ -10221,6 +10329,7 @@ const discoverPackages = (files) => {
10221
10329
  subdir,
10222
10330
  name: tree.manifest.name,
10223
10331
  description: tree.manifest.description,
10332
+ ...tree.manifest.displayName ? { displayName: tree.manifest.displayName } : {},
10224
10333
  isTemplate: validation.ok,
10225
10334
  templateErrors: validation.ok || !claimsTemplate ? [] : validation.errors,
10226
10335
  counts: countTree(tree),
@@ -10448,6 +10557,7 @@ const TEMPLATE_INDEX_FORMAT = "busabase-template-index@1";
10448
10557
  const toEntry = (found, airapps) => ({
10449
10558
  subdir: found.subdir,
10450
10559
  name: found.name,
10560
+ ...found.displayName ? { displayName: found.displayName } : {},
10451
10561
  description: found.description,
10452
10562
  category: found.category ?? "uncategorized",
10453
10563
  ...found.risk ? { risk: found.risk } : {},
@@ -10523,6 +10633,7 @@ const compact = (value) => Object.fromEntries(Object.entries(value).filter(([, i
10523
10633
  const serializeManifest = (manifest) => toJsonFile(compact({
10524
10634
  format: manifest.format,
10525
10635
  name: manifest.name,
10636
+ displayName: manifest.displayName,
10526
10637
  description: manifest.description || void 0,
10527
10638
  version: manifest.version,
10528
10639
  author: manifest.author,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busa-cli",
3
- "version": "0.51.0",
3
+ "version": "0.52.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.51.0",
36
+ "busabase-sdk": "0.52.0",
37
37
  "commander": "^14.0.0",
38
38
  "qrcode": "^1.5.4",
39
39
  "tar-stream": "^3.1.7",