busa-cli 0.95.1 → 0.97.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 +1 -1
- package/dist/index.js +1 -1
- package/dist/{run-fBARIIox.js → run-C7tO-SDN.js} +104 -40
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as checkForUpdate, n as pkgVersion, r as runCli } from "./run-
|
|
2
|
+
import { i as checkForUpdate, n as pkgVersion, r as runCli } from "./run-C7tO-SDN.js";
|
|
3
3
|
//#region src/flush-stdio.ts
|
|
4
4
|
/**
|
|
5
5
|
* Wait for stdout/stderr to reach the other side before the process is torn down.
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as render, i as checkForUpdate, n as pkgVersion, r as runCli, t as HELP } from "./run-
|
|
1
|
+
import { a as render, i as checkForUpdate, n as pkgVersion, r as runCli, t as HELP } from "./run-C7tO-SDN.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 };
|
|
@@ -158,15 +158,15 @@ const utf8ByteLength = (value) => new TextEncoder().encode(value).length;
|
|
|
158
158
|
const customPromptLabelSchema = iStringSchema.refine((value) => iStringLocaleValues(value).every((v) => v.length <= CUSTOM_AGENT_PROMPT_LIMITS.maxLabelChars), { message: `label must be at most ${CUSTOM_AGENT_PROMPT_LIMITS.maxLabelChars} characters per locale` });
|
|
159
159
|
const customPromptBodySchema = iStringSchema.refine((value) => iStringLocaleValues(value).every((v) => utf8ByteLength(v) <= CUSTOM_AGENT_PROMPT_LIMITS.maxBodyBytes), { message: `body must be at most ${CUSTOM_AGENT_PROMPT_LIMITS.maxBodyBytes} bytes (UTF-8) per locale` });
|
|
160
160
|
/**
|
|
161
|
-
* One custom scenario prompt. `body`
|
|
162
|
-
*
|
|
163
|
-
*
|
|
161
|
+
* One custom scenario prompt. `body` is only the task: every rendered prompt
|
|
162
|
+
* opens with the node's target line, the same one a built-in prompt opens with
|
|
163
|
+
* (see `node-agent-prompts.ts`), so authors never place it and this schema
|
|
164
|
+
* interpolates nothing.
|
|
164
165
|
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* reject, it is a default to supply.
|
|
166
|
+
* `{target}` is a retired placeholder that is still accepted, because bodies
|
|
167
|
+
* saved before the target line became fixed contain it: a line that is only
|
|
168
|
+
* `{target}` is dropped (the target already opens the prompt), and one inside a
|
|
169
|
+
* sentence reads as the node's name. Rejecting it would break those prompts.
|
|
170
170
|
*/
|
|
171
171
|
const customPromptDefSchema = z.object({
|
|
172
172
|
/** Stable id, unique within this node's custom list. */
|
|
@@ -178,7 +178,7 @@ const customPromptDefSchema = z.object({
|
|
|
178
178
|
intent: customPromptIntentSchema.optional(),
|
|
179
179
|
/** Short title shown in the dialog's left list. */
|
|
180
180
|
label: customPromptLabelSchema,
|
|
181
|
-
/**
|
|
181
|
+
/** The task. The node's target line is added ahead of it when the prompt renders. */
|
|
182
182
|
body: customPromptBodySchema
|
|
183
183
|
});
|
|
184
184
|
/**
|
|
@@ -246,14 +246,15 @@ const PlaybookMatchFieldSchema = z.enum([
|
|
|
246
246
|
"body"
|
|
247
247
|
]);
|
|
248
248
|
const localeSchema = z.string().trim().min(1).max(20).describe("Locale for prompt labels and the rendered prompt body, e.g. \"en\" or \"zh-CN\". Falls back to English, then to whatever locale the prompt has.");
|
|
249
|
-
|
|
249
|
+
const playbookSearchInputShape = {
|
|
250
250
|
queries: z.array(z.string().max(PLAYBOOK_SEARCH_LIMITS.maxQueryChars)).max(PLAYBOOK_SEARCH_LIMITS.maxQueries).default([]).describe("2–5 phrasings of what the user wants, in the user's language AND in English (e.g. [\"记录客户拜访\", \"log customer visit\", \"visit\"]). Each is matched as a case-insensitive substring; an item hit by more phrasings ranks higher. Empty = browse: every playbook, nearest first (at most 30)."),
|
|
251
251
|
kinds: z.array(PlaybookKindSchema).optional().describe("Restrict to \"skill\" and/or \"prompt\". Default: both."),
|
|
252
252
|
nearNodeId: z.string().trim().min(1).optional().describe("The node the user is on. Playbooks on that node rank first, then its folder, then ancestor folders (closest first)."),
|
|
253
253
|
inNodeId: z.string().trim().min(1).optional().describe("Only return playbooks on this node or inside its subtree."),
|
|
254
254
|
limit: z.coerce.number().int().min(1).max(PLAYBOOK_SEARCH_LIMITS.maxLimit).optional().describe("Max items (1–50). Default 10 for a search; 30 for a browse."),
|
|
255
255
|
locale: localeSchema.optional()
|
|
256
|
-
}
|
|
256
|
+
};
|
|
257
|
+
z.strictObject(playbookSearchInputShape, { error: (issue) => issue.code === "unrecognized_keys" ? `Unknown key${issue.keys.length === 1 ? "" : "s"} ${issue.keys.map((key) => `"${key}"`).join(", ")}. playbooks search accepts: ${Object.keys(playbookSearchInputShape).join(", ")}${issue.keys.includes("query") ? " (did you mean \"queries\", an array of phrasings?)" : ""}.` : void 0 });
|
|
257
258
|
/** Fields shared by a search item and a get result: where the playbook lives. */
|
|
258
259
|
const playbookLocationShape = {
|
|
259
260
|
kind: PlaybookKindSchema,
|
|
@@ -635,6 +636,7 @@ const baseFieldChangeTask = {
|
|
|
635
636
|
examples: [
|
|
636
637
|
"busabase-cli bases field-change-request --base-id bas_1 --operation add --slug status --name Status --field-type select",
|
|
637
638
|
"busabase-cli bases field-change-request --base-id bas_1 --operation update --field-id fld_1 --patch-json '{\"required\":true}'",
|
|
639
|
+
"busabase-cli bases field-change-request --base-id bas_1 --operation update --field-id fld_1 --patch-json '{\"help\":{\"description\":\"Actual customer acceptance date\",\"example\":\"2026-10-14\"}}'",
|
|
638
640
|
"busabase-cli bases field-change-request --base-id bas_1 --operation reorder --field-ids fld_2 --field-ids fld_1"
|
|
639
641
|
],
|
|
640
642
|
execute: async (client, input) => {
|
|
@@ -1882,6 +1884,7 @@ const rejectMismatchedPayload = (input) => {
|
|
|
1882
1884
|
const type = input.type;
|
|
1883
1885
|
const wrong = [];
|
|
1884
1886
|
if (input.fields !== void 0 && type !== "base") wrong.push("fields");
|
|
1887
|
+
if (input.skipDefaultView !== void 0 && type !== "base") wrong.push("skipDefaultView");
|
|
1885
1888
|
if (input.body !== void 0 && type !== "doc") wrong.push("body");
|
|
1886
1889
|
if (input.assetId !== void 0 && type !== "file") wrong.push("assetId");
|
|
1887
1890
|
if ((input.files !== void 0 || input.mergeMode !== void 0) && !FILE_TREE_TYPES.has(type)) wrong.push(input.files !== void 0 ? "files" : "mergeMode");
|
|
@@ -2011,7 +2014,8 @@ const createNode = async (client, input) => {
|
|
|
2011
2014
|
if (type === "base") {
|
|
2012
2015
|
const payload = {
|
|
2013
2016
|
...common,
|
|
2014
|
-
fields: asFieldArray(input.fields) ?? []
|
|
2017
|
+
fields: asFieldArray(input.fields) ?? [],
|
|
2018
|
+
...input.skipDefaultView ? { defaultView: false } : {}
|
|
2015
2019
|
};
|
|
2016
2020
|
return client.bases.create(payload);
|
|
2017
2021
|
}
|
|
@@ -2134,6 +2138,15 @@ const nodeCreateTask = {
|
|
|
2134
2138
|
parser: "fieldSpecs"
|
|
2135
2139
|
}
|
|
2136
2140
|
},
|
|
2141
|
+
{
|
|
2142
|
+
name: "skipDefaultView",
|
|
2143
|
+
appliesWhen: {
|
|
2144
|
+
param: "type",
|
|
2145
|
+
values: ["base"]
|
|
2146
|
+
},
|
|
2147
|
+
kind: "boolean",
|
|
2148
|
+
description: "Base only. Create the Base WITHOUT its default \"Grid view\" (a table of every record and field). Only when you add your own views right after with view_change_request — a Base must keep at least one view, and the first one is what it opens on."
|
|
2149
|
+
},
|
|
2137
2150
|
{
|
|
2138
2151
|
name: "body",
|
|
2139
2152
|
kind: "string",
|
|
@@ -2195,7 +2208,7 @@ const nodeCreateTask = {
|
|
|
2195
2208
|
"workspace",
|
|
2196
2209
|
"public"
|
|
2197
2210
|
],
|
|
2198
|
-
description: "Skill/Drive/AirApp only.
|
|
2211
|
+
description: "Skill/Drive/AirApp only. Omit to follow the space default and parent folder, like any other node."
|
|
2199
2212
|
},
|
|
2200
2213
|
{
|
|
2201
2214
|
name: "version",
|
|
@@ -2224,7 +2237,7 @@ const nodeCreateTask = {
|
|
|
2224
2237
|
{
|
|
2225
2238
|
name: "agentPrompts",
|
|
2226
2239
|
kind: "json",
|
|
2227
|
-
description: "Scenario prompts shown when someone opens this node and asks an agent for something: [{\"key\":\"log-visit\",\"label\":\"Log a customer visit\",\"body\":\"
|
|
2240
|
+
description: "Scenario prompts shown when someone opens this node and asks an agent for something: [{\"key\":\"log-visit\",\"label\":\"Log a customer visit\",\"body\":\"Add 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 are added after the node type's built-in scenario prompts, so 2-5 real recurring jobs help and a generic pair only repeats the defaults: omit this when you cannot name one. Write only the task: Busabase opens every prompt with a line naming the node and space, so do not write `{target}`. 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`."
|
|
2228
2241
|
}
|
|
2229
2242
|
],
|
|
2230
2243
|
examples: [
|
|
@@ -2569,7 +2582,7 @@ const viewChangeTask = {
|
|
|
2569
2582
|
name: "view_change_request",
|
|
2570
2583
|
cliPath: ["views", "change-request"],
|
|
2571
2584
|
summary: "Propose a Base view change (create / update / delete / restore)",
|
|
2572
|
-
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 — check the response's `materialized` field to see which happened. Pass requireReview to always propose instead of merging. Views are structure, not content — 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 — the endpoint split follows whichever id identifies the target, which this task handles for you.",
|
|
2585
|
+
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 — check the response's `materialized` field to see which happened. Pass requireReview to always propose instead of merging. Views are structure, not content — 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 — the endpoint split follows whichever id identifies the target, which this task handles for you. View order is shared by everyone: a new view joins the end, `update` with `position` moves one (0 = leftmost), and the leftmost view is what the Base opens on. Every Base keeps at least one view, so deleting its last one is rejected — create the replacement first. A new Base already starts with a default \"Grid view\" (every record and field): when you are about to add your own views, create the Base with `node_create` `skipDefaultView` (REST `defaultView: false`) so your first view is the one it opens on.",
|
|
2573
2586
|
annotations: {
|
|
2574
2587
|
readOnly: false,
|
|
2575
2588
|
destructive: false
|
|
@@ -2640,6 +2653,16 @@ const viewChangeTask = {
|
|
|
2640
2653
|
},
|
|
2641
2654
|
description: "Changes to apply to the view (update only)."
|
|
2642
2655
|
},
|
|
2656
|
+
{
|
|
2657
|
+
name: "position",
|
|
2658
|
+
kind: "number",
|
|
2659
|
+
min: 0,
|
|
2660
|
+
appliesWhen: {
|
|
2661
|
+
param: "action",
|
|
2662
|
+
values: ["update"]
|
|
2663
|
+
},
|
|
2664
|
+
description: "Move the view to this 0-based index among the Base's views (update only); the others shift to make room. 0 makes it the view the Base opens on."
|
|
2665
|
+
},
|
|
2643
2666
|
{
|
|
2644
2667
|
name: "message",
|
|
2645
2668
|
kind: "string",
|
|
@@ -2664,6 +2687,7 @@ const viewChangeTask = {
|
|
|
2664
2687
|
examples: [
|
|
2665
2688
|
"busabase-cli views change-request --action create --base-id bas_1 --name \"Published\" --type table",
|
|
2666
2689
|
"busabase-cli views change-request --action create --base-id bas_1 --name \"Published\" --require-review",
|
|
2690
|
+
"busabase-cli views change-request --action update --view-id viw_1 --position 0",
|
|
2667
2691
|
"busabase-cli views change-request --action delete --view-id viw_1"
|
|
2668
2692
|
],
|
|
2669
2693
|
execute: async (client, input) => {
|
|
@@ -2689,11 +2713,12 @@ const viewChangeTask = {
|
|
|
2689
2713
|
...meta
|
|
2690
2714
|
};
|
|
2691
2715
|
if (input.action === "update") {
|
|
2692
|
-
if (!input.patch) throw new Error("action \"update\" requires patch.");
|
|
2716
|
+
if (!input.patch && input.position === void 0) throw new Error("action \"update\" requires patch or position.");
|
|
2693
2717
|
return client.views.changeRequest({
|
|
2694
2718
|
operation: "update",
|
|
2695
2719
|
...target,
|
|
2696
|
-
...input.patch
|
|
2720
|
+
...input.patch ?? {},
|
|
2721
|
+
...input.position !== void 0 ? { position: input.position } : {}
|
|
2697
2722
|
});
|
|
2698
2723
|
}
|
|
2699
2724
|
if (input.action === "delete") return client.views.changeRequest({
|
|
@@ -6445,7 +6470,13 @@ const lookupRollupSchema = z.enum([
|
|
|
6445
6470
|
"max",
|
|
6446
6471
|
"concatenate"
|
|
6447
6472
|
]).default("values");
|
|
6473
|
+
/** Public, plain-text guidance. Examples are hints, never default record values. */
|
|
6474
|
+
const fieldHelpSchema = z.object({
|
|
6475
|
+
description: z.string().max(1e3).optional(),
|
|
6476
|
+
example: z.string().max(200).optional()
|
|
6477
|
+
});
|
|
6448
6478
|
const fieldOptionsSchema = z.object({
|
|
6479
|
+
help: fieldHelpSchema.optional(),
|
|
6449
6480
|
ai: z.object({
|
|
6450
6481
|
model: z.string().optional(),
|
|
6451
6482
|
prompt: z.string().optional(),
|
|
@@ -6476,7 +6507,18 @@ const fieldOptionsSchema = z.object({
|
|
|
6476
6507
|
height: z.number().int().positive().max(1200).optional(),
|
|
6477
6508
|
providers: z.array(z.string()).optional()
|
|
6478
6509
|
}).optional(),
|
|
6479
|
-
formula: z.object({
|
|
6510
|
+
formula: z.object({
|
|
6511
|
+
expression: z.string().min(1),
|
|
6512
|
+
format: z.enum([
|
|
6513
|
+
"auto",
|
|
6514
|
+
"number",
|
|
6515
|
+
"currency",
|
|
6516
|
+
"percent",
|
|
6517
|
+
"date"
|
|
6518
|
+
]).optional(),
|
|
6519
|
+
precision: z.number().int().min(0).max(10).optional(),
|
|
6520
|
+
currency: z.string().regex(/^[A-Z]{3}$/).optional()
|
|
6521
|
+
}).optional(),
|
|
6480
6522
|
inverseFieldId: z.string().optional(),
|
|
6481
6523
|
lookup: z.object({
|
|
6482
6524
|
relationFieldSlug: z.string().min(1).describe("Slug of a `relation` field on THIS Base — the hop to follow."),
|
|
@@ -6528,6 +6570,7 @@ z.object({
|
|
|
6528
6570
|
required: z.boolean().default(false),
|
|
6529
6571
|
options: fieldOptionsSchema.optional().default({})
|
|
6530
6572
|
})).default([]),
|
|
6573
|
+
defaultView: z.boolean().optional().describe("Whether the new Base gets its default \"Grid view\" (a table view showing every record and field). Defaults to true. Pass false only when you add your own views right away — a Base must always keep at least one view, and the leftmost one is what it opens on."),
|
|
6531
6574
|
autoMerge: z.boolean().optional()
|
|
6532
6575
|
});
|
|
6533
6576
|
const createBaseFieldInputSchema = z.object({
|
|
@@ -6566,7 +6609,9 @@ const updateFieldChangeRequestInputSchema = z.object({
|
|
|
6566
6609
|
patch: z.object({
|
|
6567
6610
|
name: fieldNameSchema.optional(),
|
|
6568
6611
|
required: z.boolean().optional(),
|
|
6569
|
-
options: fieldOptionsSchema.optional(),
|
|
6612
|
+
options: fieldOptionsSchema.removeDefault().optional(),
|
|
6613
|
+
/** Applied against current options at merge time; null removes all guidance. */
|
|
6614
|
+
help: fieldHelpSchema.nullable().optional(),
|
|
6570
6615
|
/**
|
|
6571
6616
|
* Not a patch key — rejected on purpose, and the only key here that is.
|
|
6572
6617
|
*
|
|
@@ -6785,9 +6830,9 @@ z.object({
|
|
|
6785
6830
|
createdAt: z.string()
|
|
6786
6831
|
});
|
|
6787
6832
|
z.object({
|
|
6788
|
-
parentId: z.string().nullable().optional().describe("
|
|
6789
|
-
depth: z.coerce.number().int().min(1).max(5).optional().describe("How many levels beneath the start point to eagerly include (default 2 once either field is set). Capped at 5."),
|
|
6790
|
-
status: z.enum(["active", "archived"]).optional().default("active").describe("`active` walks the live TREE. `archived` returns a FLAT list of soft-archived nodes (the Trash view) with no parent/depth walk — so the response shape you can rely on differs between the two, not just the rows."),
|
|
6833
|
+
parentId: z.string().nullable().optional().describe("Folder to start from: a node id or a folder slug. Omit or null to start from the space root. An unknown ref is a 404 and a node that is not a folder is a 400 — never an empty list. With `types`, scopes the flat list to this folder's subtree."),
|
|
6834
|
+
depth: z.coerce.number().int().min(1).max(5).optional().describe("How many levels beneath the start point to eagerly include (default 2 once either field is set). Capped at 5. With `types` and `parentId`, how many levels of the folder's subtree to search (default: all of it)."),
|
|
6835
|
+
status: z.enum(["active", "archived"]).optional().default("active").describe("`active` walks the live TREE. `archived` returns a FLAT list of soft-archived nodes (the Trash view) with no parent/depth walk — so the response shape you can rely on differs between the two, not just the rows. `parentId` with `archived` is a 400."),
|
|
6791
6836
|
/**
|
|
6792
6837
|
* Narrow to specific node types and return a FLAT list of lightweight node
|
|
6793
6838
|
* summaries (`children: []`) instead of walking the tree. This is what
|
|
@@ -6811,7 +6856,7 @@ z.object({
|
|
|
6811
6856
|
* bare string, not a 1-element array — only a REPEATED occurrence
|
|
6812
6857
|
* (`?types=doc&types=file`) becomes an array. Accept both and normalize.
|
|
6813
6858
|
*/
|
|
6814
|
-
types: z.union([z.array(z.enum(NODE_TYPES)), z.enum(NODE_TYPES)]).transform((value) => Array.isArray(value) ? value : [value]).optional().describe("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}.")
|
|
6859
|
+
types: z.union([z.array(z.enum(NODE_TYPES)), z.enum(NODE_TYPES)]).transform((value) => Array.isArray(value) ? value : [value]).optional().describe("Return a flat list of lightweight summaries for these node types instead of the tree — across the space, or within `parentId`'s subtree when given. Read one node's full detail with GET /nodes/{nodeId}.")
|
|
6815
6860
|
}).optional();
|
|
6816
6861
|
z.object({
|
|
6817
6862
|
nodeId: z.string().describe("The node walked UPWARDS from — the possible descendant."),
|
|
@@ -7102,7 +7147,7 @@ const changeRequestSchema = z.object({
|
|
|
7102
7147
|
sourceAttribution: sourceAttributionSchema.nullable().optional(),
|
|
7103
7148
|
sourceMeta: z.record(z.string(), z.unknown()),
|
|
7104
7149
|
reviewPolicySnapshot: z.record(z.string(), z.unknown()),
|
|
7105
|
-
mergeSummary: z.record(z.string(), z.unknown()),
|
|
7150
|
+
mergeSummary: z.record(z.string(), z.unknown()).describe("Filled in when the change request merges. `slugChanges` (present only when non-empty) lists items a restore brought back under a new slug because another item took the old one while they were in the Trash: `{ nodeId, nodeType, name, fromSlug, toSlug }`. Read the new slug from here instead of assuming the old one."),
|
|
7106
7151
|
rejectedReason: z.string().nullable(),
|
|
7107
7152
|
reviewedAt: z.string().nullable(),
|
|
7108
7153
|
mergedAt: z.string().nullable(),
|
|
@@ -7115,6 +7160,7 @@ const changeRequestSchema = z.object({
|
|
|
7115
7160
|
operationCount: z.number(),
|
|
7116
7161
|
reviews: z.array(reviewSchema)
|
|
7117
7162
|
});
|
|
7163
|
+
changeRequestSchema.extend({ materialized: z.literal(false) });
|
|
7118
7164
|
z.object({
|
|
7119
7165
|
changeRequest: changeRequestSchema,
|
|
7120
7166
|
trigger: z.enum(["changes_requested", "ai_mention"]),
|
|
@@ -7339,7 +7385,7 @@ z.object({
|
|
|
7339
7385
|
autoMerge: destructiveAutoMerge("Archiving is reversible: the record leaves every listing but is restored intact by `operation: \"restore\"`.")
|
|
7340
7386
|
});
|
|
7341
7387
|
z.object({
|
|
7342
|
-
fields: z.record(z.string(), z.unknown()).describe("Updated field values keyed by field slug. If you set the base's PRIMARY field (its first field), keep it a short human-readable name — it is the record's display title everywhere."),
|
|
7388
|
+
fields: z.record(z.string(), z.unknown()).describe("Updated field values keyed by field slug. A field id or its exact display name (any language, unambiguous) is mapped to the slug; a write in which no key names a field of the Base is a 400 listing the valid slugs. If you set the base's PRIMARY field (its first field), keep it a short human-readable name — it is the record's display title everywhere."),
|
|
7343
7389
|
message: z.string().optional().default("Revise operation").describe("Explanation shown to the human reviewer. Write a conventional-commit style subject — imperative verb + what + why, e.g. \"Update deal stage to qualified — demo booked for July 8\"."),
|
|
7344
7390
|
author: z.string().optional().default("local-producer"),
|
|
7345
7391
|
baseCommitId: z.string().optional()
|
|
@@ -7595,6 +7641,7 @@ z.object({
|
|
|
7595
7641
|
type: viewTypeSchema,
|
|
7596
7642
|
config: viewConfigSchema,
|
|
7597
7643
|
status: z.enum(["active", "archived"]),
|
|
7644
|
+
position: z.number().int(),
|
|
7598
7645
|
createdBy: z.string(),
|
|
7599
7646
|
createdByUser: userRefSchema.nullable().optional().default(null),
|
|
7600
7647
|
archivedAt: z.string().nullable(),
|
|
@@ -7628,6 +7675,7 @@ const updateViewInputSchema = z.object({
|
|
|
7628
7675
|
message: z.string().optional().default("Update view"),
|
|
7629
7676
|
name: z.string().min(1).optional(),
|
|
7630
7677
|
type: viewTypeSchema.optional(),
|
|
7678
|
+
position: z.number().int().min(0).optional().describe("Move the view to this 0-based index among the Base's active views; the others shift to make room. The order is shared by everyone, and the leftmost view (0) is the one the Base opens on. Out-of-range values clamp to the end."),
|
|
7631
7679
|
submittedBy: z.string().optional().default("local-producer"),
|
|
7632
7680
|
autoMerge: autoMergeSchema
|
|
7633
7681
|
});
|
|
@@ -7872,6 +7920,7 @@ const PackageDocFrontmatterSchema = z.object({
|
|
|
7872
7920
|
* so choice ids survive and record values referencing them need no remap.
|
|
7873
7921
|
*/
|
|
7874
7922
|
const PackageFieldOptionsSchema = z.object({
|
|
7923
|
+
help: fieldHelpSchema.optional(),
|
|
7875
7924
|
ai: z.object({
|
|
7876
7925
|
model: z.string().optional(),
|
|
7877
7926
|
prompt: z.string().optional(),
|
|
@@ -9647,7 +9696,7 @@ const applyAgentPrompts = async (client, nodeId, node, state) => {
|
|
|
9647
9696
|
const agentPrompts = node.agentPrompts;
|
|
9648
9697
|
if (!agentPrompts?.length) return;
|
|
9649
9698
|
if (!nodeId) {
|
|
9650
|
-
state.warnings.push(`"${node.slug}" installs as a change request, so its ${agentPrompts.length} agent prompt(s) were not applied. After merging it, run \`busabase-cli nodes set-agent-prompts\` for that node, or re-install
|
|
9699
|
+
state.warnings.push(`"${node.slug}" installs as a change request, so its ${agentPrompts.length} agent prompt(s) were not applied. After merging it, run \`busabase-cli nodes set-agent-prompts\` for that node, or re-install without asking for review (drop --require-review, or leave "Send the content for review first" off).`);
|
|
9651
9700
|
return;
|
|
9652
9701
|
}
|
|
9653
9702
|
try {
|
|
@@ -9781,6 +9830,7 @@ const createBase = async (client, node, parentNodeId, options, state, bases, fie
|
|
|
9781
9830
|
name: node.name,
|
|
9782
9831
|
description: node.description,
|
|
9783
9832
|
fields: immediateFields,
|
|
9833
|
+
...node.base.views.length > 0 ? { defaultView: false } : {},
|
|
9784
9834
|
autoMerge: true
|
|
9785
9835
|
});
|
|
9786
9836
|
if (!result.materialized) throw new Error(`Base "${node.slug}" was requested immediately but the server returned a pending change request; install cannot attach its views or records to it.`);
|
|
@@ -10308,7 +10358,10 @@ const collectBase = async (client, source, common, options) => {
|
|
|
10308
10358
|
const base = await client.bases.get({ baseId });
|
|
10309
10359
|
if (!base) throw new Error(`Base node "${source.slug}" has no Base behind it (looked up ${baseId}). The space may be mid-migration; re-run export.`);
|
|
10310
10360
|
const allViews = await client.bases.listViews({ baseId: base.id });
|
|
10311
|
-
const views = allViews.filter((view) => view.slug.length > 0)
|
|
10361
|
+
const views = allViews.filter((view) => view.slug.length > 0).map((view, index) => ({
|
|
10362
|
+
view,
|
|
10363
|
+
index
|
|
10364
|
+
})).sort((left, right) => (left.view.position ?? 0) - (right.view.position ?? 0) || left.index - right.index).map(({ view }) => view);
|
|
10312
10365
|
if (views.length !== allViews.length) options.warn(`Base "${common.slug}" has ${allViews.length - views.length} view(s) with no slug — they were dropped (predates the slug requirement; rename it in the UI to keep it).`);
|
|
10313
10366
|
const { baseSlugById, fieldSlugById } = await buildIndexes(client, options.baseResourceKeys ?? /* @__PURE__ */ new Map());
|
|
10314
10367
|
const fields = [...base.fields].sort((a, b) => a.position - b.position).map((field) => toPackageField(field, {
|
|
@@ -10971,7 +11024,7 @@ const findPlanBlockers = (plan, autoMerge) => {
|
|
|
10971
11024
|
const list = plan.collisions.filter((collision) => !collision.renamedTo).map((collision) => ` • ${collision.kind} "${collision.slug}" (${collision.path})`).join("\n");
|
|
10972
11025
|
blockers.push(`Install would collide with content that already exists:\n${list}\n\nNothing was created. Re-run with --rename to install the colliding items under suffixed slugs (e.g. "-2"), or pick a different --into-folder.`);
|
|
10973
11026
|
}
|
|
10974
|
-
if (plan.requiresAutoMerge && !autoMerge) blockers.push("This package's records carry relation values, and a relation stores the ids of the records it points at — which only exist once the records are merged. Installing review-first would leave every relation empty.\n\
|
|
11027
|
+
if (plan.requiresAutoMerge && !autoMerge) blockers.push("This package's records carry relation values, and a relation stores the ids of the records it points at — which only exist once the records are merged. Installing review-first would leave every relation empty.\n\nInstall it without asking for review: drop --require-review, or leave \"Send the content for review first\" off in the dashboard. Records are then created immediately. A credential that can only propose changes cannot install this package; use one with write access to the space.");
|
|
10975
11028
|
const unsatisfiable = findUnsatisfiableRequiredFields(plan);
|
|
10976
11029
|
if (unsatisfiable.length > 0) blockers.push(`This package cannot satisfy its own required fields:\n${unsatisfiable.join("\n")}\n\nThe server rejects these while writing records, partway through the install. Re-export the package (export relaxes fields it cannot fill), or edit the Base definitions in the package to make them optional.`);
|
|
10977
11030
|
return blockers;
|
|
@@ -12243,6 +12296,11 @@ const PROCEDURE_PERMISSION_POLICY = {
|
|
|
12243
12296
|
"agents.connections.list": workspace("manage"),
|
|
12244
12297
|
"agents.disconnect": workspace("manage"),
|
|
12245
12298
|
"agents.deleteHistory": workspace("manage"),
|
|
12299
|
+
"agents.routerEnrollment.grants": workspace("manage"),
|
|
12300
|
+
"agents.routerEnrollment.mint": workspace("manage"),
|
|
12301
|
+
"agents.routerEnrollment.status": workspace("manage"),
|
|
12302
|
+
"agents.routerEnrollment.complete": workspace("manage"),
|
|
12303
|
+
"agents.routerEnrollment.cancel": workspace("manage"),
|
|
12246
12304
|
"agents.sessions.list": workspace("manage"),
|
|
12247
12305
|
"agents.sessions.listPaged": workspace("manage"),
|
|
12248
12306
|
"agents.sessions.create": workspace("manage"),
|
|
@@ -12256,6 +12314,7 @@ const PROCEDURE_PERMISSION_POLICY = {
|
|
|
12256
12314
|
"changeRequests.listPage": node("read"),
|
|
12257
12315
|
"changeRequests.inboxSnapshot": node("read"),
|
|
12258
12316
|
"changeRequests.counts": node("read"),
|
|
12317
|
+
"changeRequests.reviewCount": node("read"),
|
|
12259
12318
|
"changeRequests.get": node("read"),
|
|
12260
12319
|
"changeRequests.createPreviewLink": node("changeRequest"),
|
|
12261
12320
|
"records.list": node("read"),
|
|
@@ -12607,6 +12666,7 @@ Quick reference — every path is relative to the base URL above; worked example
|
|
|
12607
12666
|
| Bulk create records CR | \`POST /api/v1/bases/:baseId/records/bulk-change-request\` | propose many records (≤1000) as ONE CR |
|
|
12608
12667
|
| Bulk update records CR | \`POST /api/v1/bases/:baseId/records/bulk-update-change-request\` | partially update many existing records (≤1000) as ONE atomic CR |
|
|
12609
12668
|
| Create node CR | \`POST /api/v1/nodes/change-requests\` | propose folder / Skill structure changes (supports in-CR \`ref\`/\`parentNodeRef\`) |
|
|
12669
|
+
| View CR | \`POST /api/v1/views/change-requests\` | create / update / delete / restore a Base view. Order is shared: \`update\` with \`position\` reorders, the leftmost view is what the Base opens on, and a Base's last view can't be deleted. A new Base starts with a "Grid view" unless created with \`"defaultView": false\` |
|
|
12610
12670
|
| Read Skill files | \`GET /api/v1/file-trees/:nodeId/files/:filePath\` | read a Skill file (same route for Drives and AirApps) |
|
|
12611
12671
|
| Create Skill file CR | \`POST /api/v1/file-trees/:nodeId/change-requests\` | propose a Skill file edit |
|
|
12612
12672
|
| Request asset upload | \`POST /api/v1/assets/upload-urls\` | request an upload target for attachment-field files |
|
|
@@ -12957,7 +13017,9 @@ curl ${base}/api/v1/openapi.json
|
|
|
12957
13017
|
\`\`\`
|
|
12958
13018
|
|
|
12959
13019
|
It is large — **do not load the whole file into context.** Pull out only the path you need
|
|
12960
|
-
(e.g. \`curl ${base}/api/v1/openapi.json | jq '.paths["/api/v1/records"]'\`)
|
|
13020
|
+
(e.g. \`curl ${base}/api/v1/openapi.json | jq '.paths["/api/v1/records"]'\`); shared types such as
|
|
13021
|
+
\`ChangeRequest\`, \`Base\`, and \`Record\` are \`$ref\`s into \`.components.schemas\` (e.g.
|
|
13022
|
+
\`jq '.components.schemas.ChangeRequest'\`). Or browse the
|
|
12961
13023
|
interactive docs at \`${base}/api/v1/doc\`. MCP-capable agents can skip docs entirely and
|
|
12962
13024
|
connect to \`${base}/api/mcp\` (Streamable HTTP) — the most context-efficient path.
|
|
12963
13025
|
|
|
@@ -13050,9 +13112,9 @@ ${authLine} -H 'content-type: application/json' \\
|
|
|
13050
13112
|
--data '{
|
|
13051
13113
|
"agentPrompts": [
|
|
13052
13114
|
{ "key": "log-visit", "label": "Log a customer visit",
|
|
13053
|
-
"body": "
|
|
13115
|
+
"body": "Add a visit record with the date I give, the contact I name, and a one-line summary." },
|
|
13054
13116
|
{ "key": "quarter-recap", "intent": "read-only", "label": "What did we discuss this quarter",
|
|
13055
|
-
"body": "
|
|
13117
|
+
"body": "Summarize every visit to the account I name since the quarter started." }
|
|
13056
13118
|
]
|
|
13057
13119
|
}'
|
|
13058
13120
|
\`\`\`
|
|
@@ -13067,8 +13129,8 @@ Four rules, each of which is a way this goes wrong:
|
|
|
13067
13129
|
write nothing — the defaults already cover the generic work.
|
|
13068
13130
|
3. **Only write them when you know the job.** You usually do — the user just told you why they
|
|
13069
13131
|
wanted this node. A scratch table, a one-off Doc, a folder that only groups things: no prompts.
|
|
13070
|
-
4.
|
|
13071
|
-
|
|
13132
|
+
4. **Write only the task.** Busabase opens every prompt with a line naming the node and space, so
|
|
13133
|
+
do not write \`{target}\` — the target is no longer yours to place.
|
|
13072
13134
|
|
|
13073
13135
|
Prompts are addressed by node id, so they are a second call after the node exists. When your
|
|
13074
13136
|
create was proposed for review instead of merged, there is no node id yet — say so, and write
|
|
@@ -15417,10 +15479,11 @@ function buildProgram(state = {}) {
|
|
|
15417
15479
|
Answers "what do I send, and what comes back" without fetching anything. The
|
|
15418
15480
|
contract is compiled into this CLI, so \`schema\` works offline and unauthenticated.
|
|
15419
15481
|
|
|
15420
|
-
Prefer it over \`busabase-cli openapi\`: that returns the WHOLE document —
|
|
15421
|
-
across
|
|
15422
|
-
reading one endpoint's arguments out of it costs
|
|
15423
|
-
|
|
15482
|
+
Prefer it over \`busabase-cli openapi\`: that returns the WHOLE document — about
|
|
15483
|
+
240 KB across 106 endpoints as of 2026-10-20, with shared types behind \`$ref\`s
|
|
15484
|
+
into \`components.schemas\` — so reading one endpoint's arguments out of it costs
|
|
15485
|
+
tens of thousands of tokens plus chasing references. One endpoint through
|
|
15486
|
+
\`schema\` is a few KB and self-contained.
|
|
15424
15487
|
|
|
15425
15488
|
busabase-cli schema # every endpoint: id, method, path
|
|
15426
15489
|
busabase-cli schema ${sampleEndpointId()} # input + output schema
|
|
@@ -15651,8 +15714,8 @@ Each entry: { key: string (unique per node), intent?: "read-only" | "change",
|
|
|
15651
15714
|
label: string | { <locale>: string }, body: string | { <locale>: string } }.
|
|
15652
15715
|
Locale keys (when using the object form): en, zh-CN, zh-TW, ja, ko, de, fr, es,
|
|
15653
15716
|
pt — anything else fails validation as "Invalid input" on that field.
|
|
15654
|
-
"body"
|
|
15655
|
-
|
|
15717
|
+
"body" is only the task: every prompt opens with the same node/space line the
|
|
15718
|
+
built-in prompts use, added for you, so do not write "{target}". At most 50
|
|
15656
15719
|
entries per node, 80 characters per localized label, 8 KiB per localized body.
|
|
15657
15720
|
|
|
15658
15721
|
The file is validated against this exact schema BEFORE any network request —
|
|
@@ -15878,7 +15941,8 @@ manual lands as a Skill node, its nodes are stamped as belonging to that app, it
|
|
|
15878
15941
|
Base slugs are prefixed with the target folder so two templates cannot collide on
|
|
15879
15942
|
a name like "settings", and its sample rows are merged so the app is not empty
|
|
15880
15943
|
when you open it (--no-sample-records opts out). Its AirApp code and its Skill
|
|
15881
|
-
|
|
15944
|
+
follow the same rule as other content: live if you can write, held for review
|
|
15945
|
+
with --require-review.
|
|
15882
15946
|
|
|
15883
15947
|
If the URL is a repository that holds several packages rather than being one,
|
|
15884
15948
|
install lists them and you pick with --skill <name>.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "busa-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.97.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.97.0",
|
|
37
37
|
"commander": "^14.0.0",
|
|
38
38
|
"qrcode": "^1.5.4",
|
|
39
39
|
"tar-stream": "^3.1.7",
|