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-
|
|
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-
|
|
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
|
|
1833
|
-
const
|
|
1834
|
-
|
|
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 `
|
|
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.
|
|
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.
|
|
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",
|