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 CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as checkForUpdate, n as pkgVersion, r as runCli } from "./run-B3hHU9aE.js";
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-B3hHU9aE.js";
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`'s `{target}` placeholder is substituted at
162
- * render time with the same target string `PromptDef.body(target)` receives
163
- * today (see `node-agent-prompts.ts`) — this schema does not interpolate it.
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
- * The placeholder is OPTIONAL and chooses placement only: a `body` that never
166
- * mentions `{target}` gets the target line prepended as its first paragraph, so
167
- * a custom prompt can never reach an agent without naming the node it acts on.
168
- * That is why the schema does not require it — forgetting it is not an error to
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
- /** Template text; "{target}" is substituted with the rendered target line. */
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
- z.object({
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\":\"{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`."
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("Node to start from. Omit or null to start from the space root."),
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"]'\`), or browse the
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": "{target}\\n\\nAdd a visit record with the date I give, the contact I name, and a one-line summary." },
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": "{target}\\n\\nSummarize every visit to the account I name since the quarter started." }
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. **\`{target}\` expands to a full sentence** naming the node and space, so give it its own line.
13098
- Dropped mid-sentence it reads as a run-on with two full stops.
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 — 936 KB
15448
- across 103 endpoints as of 2026-09-04, with every type inlined per endpoint — so
15449
- reading one endpoint's arguments out of it costs roughly 234k tokens. One
15450
- endpoint through \`schema\` is a few KB.
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" may contain a literal "{target}" placeholder, substituted at render time
15682
- with the same node/space description every built-in prompt uses. At most 50
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.96.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.96.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",