busa-cli 0.96.0 → 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-B3hHU9aE.js → run-C7tO-SDN.js} +69 -33
- 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,
|
|
@@ -1883,6 +1884,7 @@ const rejectMismatchedPayload = (input) => {
|
|
|
1883
1884
|
const type = input.type;
|
|
1884
1885
|
const wrong = [];
|
|
1885
1886
|
if (input.fields !== void 0 && type !== "base") wrong.push("fields");
|
|
1887
|
+
if (input.skipDefaultView !== void 0 && type !== "base") wrong.push("skipDefaultView");
|
|
1886
1888
|
if (input.body !== void 0 && type !== "doc") wrong.push("body");
|
|
1887
1889
|
if (input.assetId !== void 0 && type !== "file") wrong.push("assetId");
|
|
1888
1890
|
if ((input.files !== void 0 || input.mergeMode !== void 0) && !FILE_TREE_TYPES.has(type)) wrong.push(input.files !== void 0 ? "files" : "mergeMode");
|
|
@@ -2012,7 +2014,8 @@ const createNode = async (client, input) => {
|
|
|
2012
2014
|
if (type === "base") {
|
|
2013
2015
|
const payload = {
|
|
2014
2016
|
...common,
|
|
2015
|
-
fields: asFieldArray(input.fields) ?? []
|
|
2017
|
+
fields: asFieldArray(input.fields) ?? [],
|
|
2018
|
+
...input.skipDefaultView ? { defaultView: false } : {}
|
|
2016
2019
|
};
|
|
2017
2020
|
return client.bases.create(payload);
|
|
2018
2021
|
}
|
|
@@ -2135,6 +2138,15 @@ const nodeCreateTask = {
|
|
|
2135
2138
|
parser: "fieldSpecs"
|
|
2136
2139
|
}
|
|
2137
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
|
+
},
|
|
2138
2150
|
{
|
|
2139
2151
|
name: "body",
|
|
2140
2152
|
kind: "string",
|
|
@@ -2225,7 +2237,7 @@ const nodeCreateTask = {
|
|
|
2225
2237
|
{
|
|
2226
2238
|
name: "agentPrompts",
|
|
2227
2239
|
kind: "json",
|
|
2228
|
-
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`."
|
|
2229
2241
|
}
|
|
2230
2242
|
],
|
|
2231
2243
|
examples: [
|
|
@@ -2570,7 +2582,7 @@ const viewChangeTask = {
|
|
|
2570
2582
|
name: "view_change_request",
|
|
2571
2583
|
cliPath: ["views", "change-request"],
|
|
2572
2584
|
summary: "Propose a Base view change (create / update / delete / restore)",
|
|
2573
|
-
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.",
|
|
2574
2586
|
annotations: {
|
|
2575
2587
|
readOnly: false,
|
|
2576
2588
|
destructive: false
|
|
@@ -2641,6 +2653,16 @@ const viewChangeTask = {
|
|
|
2641
2653
|
},
|
|
2642
2654
|
description: "Changes to apply to the view (update only)."
|
|
2643
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
|
+
},
|
|
2644
2666
|
{
|
|
2645
2667
|
name: "message",
|
|
2646
2668
|
kind: "string",
|
|
@@ -2665,6 +2687,7 @@ const viewChangeTask = {
|
|
|
2665
2687
|
examples: [
|
|
2666
2688
|
"busabase-cli views change-request --action create --base-id bas_1 --name \"Published\" --type table",
|
|
2667
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",
|
|
2668
2691
|
"busabase-cli views change-request --action delete --view-id viw_1"
|
|
2669
2692
|
],
|
|
2670
2693
|
execute: async (client, input) => {
|
|
@@ -2690,11 +2713,12 @@ const viewChangeTask = {
|
|
|
2690
2713
|
...meta
|
|
2691
2714
|
};
|
|
2692
2715
|
if (input.action === "update") {
|
|
2693
|
-
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.");
|
|
2694
2717
|
return client.views.changeRequest({
|
|
2695
2718
|
operation: "update",
|
|
2696
2719
|
...target,
|
|
2697
|
-
...input.patch
|
|
2720
|
+
...input.patch ?? {},
|
|
2721
|
+
...input.position !== void 0 ? { position: input.position } : {}
|
|
2698
2722
|
});
|
|
2699
2723
|
}
|
|
2700
2724
|
if (input.action === "delete") return client.views.changeRequest({
|
|
@@ -6546,6 +6570,7 @@ z.object({
|
|
|
6546
6570
|
required: z.boolean().default(false),
|
|
6547
6571
|
options: fieldOptionsSchema.optional().default({})
|
|
6548
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."),
|
|
6549
6574
|
autoMerge: z.boolean().optional()
|
|
6550
6575
|
});
|
|
6551
6576
|
const createBaseFieldInputSchema = z.object({
|
|
@@ -6805,9 +6830,9 @@ z.object({
|
|
|
6805
6830
|
createdAt: z.string()
|
|
6806
6831
|
});
|
|
6807
6832
|
z.object({
|
|
6808
|
-
parentId: z.string().nullable().optional().describe("
|
|
6809
|
-
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."),
|
|
6810
|
-
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."),
|
|
6811
6836
|
/**
|
|
6812
6837
|
* Narrow to specific node types and return a FLAT list of lightweight node
|
|
6813
6838
|
* summaries (`children: []`) instead of walking the tree. This is what
|
|
@@ -6831,7 +6856,7 @@ z.object({
|
|
|
6831
6856
|
* bare string, not a 1-element array — only a REPEATED occurrence
|
|
6832
6857
|
* (`?types=doc&types=file`) becomes an array. Accept both and normalize.
|
|
6833
6858
|
*/
|
|
6834
|
-
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}.")
|
|
6835
6860
|
}).optional();
|
|
6836
6861
|
z.object({
|
|
6837
6862
|
nodeId: z.string().describe("The node walked UPWARDS from — the possible descendant."),
|
|
@@ -7135,6 +7160,7 @@ const changeRequestSchema = z.object({
|
|
|
7135
7160
|
operationCount: z.number(),
|
|
7136
7161
|
reviews: z.array(reviewSchema)
|
|
7137
7162
|
});
|
|
7163
|
+
changeRequestSchema.extend({ materialized: z.literal(false) });
|
|
7138
7164
|
z.object({
|
|
7139
7165
|
changeRequest: changeRequestSchema,
|
|
7140
7166
|
trigger: z.enum(["changes_requested", "ai_mention"]),
|
|
@@ -7359,7 +7385,7 @@ z.object({
|
|
|
7359
7385
|
autoMerge: destructiveAutoMerge("Archiving is reversible: the record leaves every listing but is restored intact by `operation: \"restore\"`.")
|
|
7360
7386
|
});
|
|
7361
7387
|
z.object({
|
|
7362
|
-
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."),
|
|
7363
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\"."),
|
|
7364
7390
|
author: z.string().optional().default("local-producer"),
|
|
7365
7391
|
baseCommitId: z.string().optional()
|
|
@@ -7615,6 +7641,7 @@ z.object({
|
|
|
7615
7641
|
type: viewTypeSchema,
|
|
7616
7642
|
config: viewConfigSchema,
|
|
7617
7643
|
status: z.enum(["active", "archived"]),
|
|
7644
|
+
position: z.number().int(),
|
|
7618
7645
|
createdBy: z.string(),
|
|
7619
7646
|
createdByUser: userRefSchema.nullable().optional().default(null),
|
|
7620
7647
|
archivedAt: z.string().nullable(),
|
|
@@ -7648,6 +7675,7 @@ const updateViewInputSchema = z.object({
|
|
|
7648
7675
|
message: z.string().optional().default("Update view"),
|
|
7649
7676
|
name: z.string().min(1).optional(),
|
|
7650
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."),
|
|
7651
7679
|
submittedBy: z.string().optional().default("local-producer"),
|
|
7652
7680
|
autoMerge: autoMergeSchema
|
|
7653
7681
|
});
|
|
@@ -9802,6 +9830,7 @@ const createBase = async (client, node, parentNodeId, options, state, bases, fie
|
|
|
9802
9830
|
name: node.name,
|
|
9803
9831
|
description: node.description,
|
|
9804
9832
|
fields: immediateFields,
|
|
9833
|
+
...node.base.views.length > 0 ? { defaultView: false } : {},
|
|
9805
9834
|
autoMerge: true
|
|
9806
9835
|
});
|
|
9807
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.`);
|
|
@@ -10329,7 +10358,10 @@ const collectBase = async (client, source, common, options) => {
|
|
|
10329
10358
|
const base = await client.bases.get({ baseId });
|
|
10330
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.`);
|
|
10331
10360
|
const allViews = await client.bases.listViews({ baseId: base.id });
|
|
10332
|
-
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);
|
|
10333
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).`);
|
|
10334
10366
|
const { baseSlugById, fieldSlugById } = await buildIndexes(client, options.baseResourceKeys ?? /* @__PURE__ */ new Map());
|
|
10335
10367
|
const fields = [...base.fields].sort((a, b) => a.position - b.position).map((field) => toPackageField(field, {
|
|
@@ -12634,6 +12666,7 @@ Quick reference — every path is relative to the base URL above; worked example
|
|
|
12634
12666
|
| Bulk create records CR | \`POST /api/v1/bases/:baseId/records/bulk-change-request\` | propose many records (≤1000) as ONE CR |
|
|
12635
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 |
|
|
12636
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\` |
|
|
12637
12670
|
| Read Skill files | \`GET /api/v1/file-trees/:nodeId/files/:filePath\` | read a Skill file (same route for Drives and AirApps) |
|
|
12638
12671
|
| Create Skill file CR | \`POST /api/v1/file-trees/:nodeId/change-requests\` | propose a Skill file edit |
|
|
12639
12672
|
| Request asset upload | \`POST /api/v1/assets/upload-urls\` | request an upload target for attachment-field files |
|
|
@@ -12984,7 +13017,9 @@ curl ${base}/api/v1/openapi.json
|
|
|
12984
13017
|
\`\`\`
|
|
12985
13018
|
|
|
12986
13019
|
It is large — **do not load the whole file into context.** Pull out only the path you need
|
|
12987
|
-
(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
|
|
12988
13023
|
interactive docs at \`${base}/api/v1/doc\`. MCP-capable agents can skip docs entirely and
|
|
12989
13024
|
connect to \`${base}/api/mcp\` (Streamable HTTP) — the most context-efficient path.
|
|
12990
13025
|
|
|
@@ -13077,9 +13112,9 @@ ${authLine} -H 'content-type: application/json' \\
|
|
|
13077
13112
|
--data '{
|
|
13078
13113
|
"agentPrompts": [
|
|
13079
13114
|
{ "key": "log-visit", "label": "Log a customer visit",
|
|
13080
|
-
"body": "
|
|
13115
|
+
"body": "Add a visit record with the date I give, the contact I name, and a one-line summary." },
|
|
13081
13116
|
{ "key": "quarter-recap", "intent": "read-only", "label": "What did we discuss this quarter",
|
|
13082
|
-
"body": "
|
|
13117
|
+
"body": "Summarize every visit to the account I name since the quarter started." }
|
|
13083
13118
|
]
|
|
13084
13119
|
}'
|
|
13085
13120
|
\`\`\`
|
|
@@ -13094,8 +13129,8 @@ Four rules, each of which is a way this goes wrong:
|
|
|
13094
13129
|
write nothing — the defaults already cover the generic work.
|
|
13095
13130
|
3. **Only write them when you know the job.** You usually do — the user just told you why they
|
|
13096
13131
|
wanted this node. A scratch table, a one-off Doc, a folder that only groups things: no prompts.
|
|
13097
|
-
4.
|
|
13098
|
-
|
|
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.
|
|
13099
13134
|
|
|
13100
13135
|
Prompts are addressed by node id, so they are a second call after the node exists. When your
|
|
13101
13136
|
create was proposed for review instead of merged, there is no node id yet — say so, and write
|
|
@@ -15444,10 +15479,11 @@ function buildProgram(state = {}) {
|
|
|
15444
15479
|
Answers "what do I send, and what comes back" without fetching anything. The
|
|
15445
15480
|
contract is compiled into this CLI, so \`schema\` works offline and unauthenticated.
|
|
15446
15481
|
|
|
15447
|
-
Prefer it over \`busabase-cli openapi\`: that returns the WHOLE document —
|
|
15448
|
-
across
|
|
15449
|
-
reading one endpoint's arguments out of it costs
|
|
15450
|
-
|
|
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.
|
|
15451
15487
|
|
|
15452
15488
|
busabase-cli schema # every endpoint: id, method, path
|
|
15453
15489
|
busabase-cli schema ${sampleEndpointId()} # input + output schema
|
|
@@ -15678,8 +15714,8 @@ Each entry: { key: string (unique per node), intent?: "read-only" | "change",
|
|
|
15678
15714
|
label: string | { <locale>: string }, body: string | { <locale>: string } }.
|
|
15679
15715
|
Locale keys (when using the object form): en, zh-CN, zh-TW, ja, ko, de, fr, es,
|
|
15680
15716
|
pt — anything else fails validation as "Invalid input" on that field.
|
|
15681
|
-
"body"
|
|
15682
|
-
|
|
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
|
|
15683
15719
|
entries per node, 80 characters per localized label, 8 KiB per localized body.
|
|
15684
15720
|
|
|
15685
15721
|
The file is validated against this exact schema BEFORE any network request —
|
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",
|