busa-cli 0.80.0 → 0.90.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-CqgevSM9.js → run-CLlUJcaW.js} +572 -42
- 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-CLlUJcaW.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-CLlUJcaW.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 };
|
|
@@ -29,7 +29,8 @@ const LocaleSchema = z.enum({
|
|
|
29
29
|
"de",
|
|
30
30
|
"fr",
|
|
31
31
|
"es",
|
|
32
|
-
"pt"
|
|
32
|
+
"pt",
|
|
33
|
+
"vi"
|
|
33
34
|
],
|
|
34
35
|
extendLocales: [
|
|
35
36
|
"en",
|
|
@@ -85,9 +86,9 @@ const LocaleSchema = z.enum({
|
|
|
85
86
|
* - ZH: 拼接所有语言值以便展示/调试(顺序不保证)。
|
|
86
87
|
*
|
|
87
88
|
* Fallback behavior (iStringParse) / 回退策略(iStringParse)
|
|
88
|
-
* - EN: If str is a string, return it. If it is a record: prefer requested locale; else
|
|
89
|
+
* - EN: If str is a string, return it. If it is a record: prefer requested locale; else "en"; else any available locale.
|
|
89
90
|
* Warn and return empty string if no suitable value exists.
|
|
90
|
-
* - ZH:
|
|
91
|
+
* - ZH: 若为纯字符串,原样返回;若为记录:优先使用请求的语言;否则使用 "en";再退回任一可用语言。
|
|
91
92
|
* 若均不可用,会发出警告并返回空字符串。
|
|
92
93
|
*
|
|
93
94
|
* Examples / 示例
|
|
@@ -114,7 +115,7 @@ const iStringParse = (str, lang = "en") => {
|
|
|
114
115
|
if (!str) return "";
|
|
115
116
|
if (typeof str === "string") return str;
|
|
116
117
|
const locales = Object.keys(str || {});
|
|
117
|
-
const result = str[locales.
|
|
118
|
+
const result = str[locales.includes(lang) ? lang : locales.includes("en") ? "en" : locales[0] ?? "en"];
|
|
118
119
|
if (result === void 0) console.warn(`iStringParse: ${JSON.stringify(str)} has no '${lang}' or other locales`);
|
|
119
120
|
return result || "";
|
|
120
121
|
};
|
|
@@ -202,6 +203,233 @@ const customAgentPromptsSchema = z.array(customPromptDefSchema).max(CUSTOM_AGENT
|
|
|
202
203
|
});
|
|
203
204
|
});
|
|
204
205
|
//#endregion
|
|
206
|
+
//#region ../../packages/busabase-contract/src/contract/playbook-schemas.ts
|
|
207
|
+
/**
|
|
208
|
+
* Playbooks — the skills and custom node prompts a space already defines for a
|
|
209
|
+
* job — top-level, domain-agnostic schemas for `POST /playbooks/search` and
|
|
210
|
+
* `GET /playbooks/{kind}/{nodeId}`.
|
|
211
|
+
*
|
|
212
|
+
* Top-level (like `grep-schemas.ts`) rather than inside one domain's contract,
|
|
213
|
+
* because a playbook is either a Skill node or a prompt stored on ANY node type:
|
|
214
|
+
* the surface composes several domains.
|
|
215
|
+
*
|
|
216
|
+
* The server does no intent recognition. The agent sends several phrasings of
|
|
217
|
+
* what the user wants; the server matches them as case-insensitive substrings
|
|
218
|
+
* against skill name/slug/description and prompt label/body, and ranks. See
|
|
219
|
+
* `apps/busabase/content/spec/agent-playbook-discovery.md` §6.
|
|
220
|
+
*
|
|
221
|
+
* Pure zod — no logic/db imports (client-safe: pulled into the browser bundle
|
|
222
|
+
* and the RN oRPC client's type graph).
|
|
223
|
+
*/
|
|
224
|
+
/** Named so a limit change has exactly one place to edit, and error messages can cite it. */
|
|
225
|
+
const PLAYBOOK_SEARCH_LIMITS = {
|
|
226
|
+
/** Max phrasings per search. */
|
|
227
|
+
maxQueries: 8,
|
|
228
|
+
/** Max characters per phrasing. */
|
|
229
|
+
maxQueryChars: 200,
|
|
230
|
+
/** Default number of items returned for a non-empty search. */
|
|
231
|
+
defaultLimit: 10,
|
|
232
|
+
/** Hard cap on `limit`. */
|
|
233
|
+
maxLimit: 50,
|
|
234
|
+
/** An empty `queries` (browse) returns at most this many, nearest first. */
|
|
235
|
+
browseLimit: 30,
|
|
236
|
+
/** How much of a prompt body (per locale) is matched against. */
|
|
237
|
+
bodyPrefixChars: 500
|
|
238
|
+
};
|
|
239
|
+
const PlaybookKindSchema = z.enum(["skill", "prompt"]);
|
|
240
|
+
/** Why an item matched. `name`/`slug`/`description` are skill fields; `label`/`body` are prompt fields. */
|
|
241
|
+
const PlaybookMatchFieldSchema = z.enum([
|
|
242
|
+
"name",
|
|
243
|
+
"slug",
|
|
244
|
+
"description",
|
|
245
|
+
"label",
|
|
246
|
+
"body"
|
|
247
|
+
]);
|
|
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({
|
|
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
|
+
kinds: z.array(PlaybookKindSchema).optional().describe("Restrict to \"skill\" and/or \"prompt\". Default: both."),
|
|
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
|
+
inNodeId: z.string().trim().min(1).optional().describe("Only return playbooks on this node or inside its subtree."),
|
|
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
|
+
locale: localeSchema.optional()
|
|
256
|
+
});
|
|
257
|
+
/** Fields shared by a search item and a get result: where the playbook lives. */
|
|
258
|
+
const playbookLocationShape = {
|
|
259
|
+
kind: PlaybookKindSchema,
|
|
260
|
+
nodeId: z.string(),
|
|
261
|
+
nodeType: z.string(),
|
|
262
|
+
nodeName: z.string(),
|
|
263
|
+
nodeSlug: z.string(),
|
|
264
|
+
/** Folder names from the workspace root down to the node's parent. Tells two same-named playbooks apart. */
|
|
265
|
+
path: z.array(z.string())
|
|
266
|
+
};
|
|
267
|
+
const PlaybookSearchItemVOSchema = z.object({
|
|
268
|
+
...playbookLocationShape,
|
|
269
|
+
/** Prompts only: the key to pass to `playbooks.get`. */
|
|
270
|
+
key: z.string().optional(),
|
|
271
|
+
/** Prompts only: the label in the requested locale (fallback `en`, then any). */
|
|
272
|
+
label: z.string().optional(),
|
|
273
|
+
/** Prompts only: `read-only` never writes; `change` goes through a change request. */
|
|
274
|
+
intent: z.enum(["read-only", "change"]).optional(),
|
|
275
|
+
/** Skills only: the skill's name. */
|
|
276
|
+
name: z.string().optional(),
|
|
277
|
+
/** Skills only: the skill's description (follows its SKILL.md frontmatter). */
|
|
278
|
+
description: z.string().optional(),
|
|
279
|
+
matchedOn: z.array(PlaybookMatchFieldSchema),
|
|
280
|
+
/** For debugging and tests. Rely on the ORDER of `items`, not on this number. */
|
|
281
|
+
score: z.number(),
|
|
282
|
+
updatedAt: z.string()
|
|
283
|
+
});
|
|
284
|
+
z.object({
|
|
285
|
+
items: z.array(PlaybookSearchItemVOSchema),
|
|
286
|
+
/** Every matching playbook the caller can read — not just the ones returned. */
|
|
287
|
+
total: z.number().int(),
|
|
288
|
+
/** True when `total` is larger than `items`. Narrow the search rather than paging. */
|
|
289
|
+
truncated: z.boolean(),
|
|
290
|
+
coverage: z.object({
|
|
291
|
+
/** Skill nodes considered (after permission, `kinds`, and `inNodeId` filters). */
|
|
292
|
+
skillsScanned: z.number().int(),
|
|
293
|
+
/** Nodes carrying custom prompts that were considered. */
|
|
294
|
+
promptNodesScanned: z.number().int()
|
|
295
|
+
})
|
|
296
|
+
});
|
|
297
|
+
z.object({
|
|
298
|
+
kinds: z.array(PlaybookKindSchema).optional().describe("Restrict to \"skill\" and/or \"prompt\". Default: both."),
|
|
299
|
+
locale: localeSchema.optional()
|
|
300
|
+
});
|
|
301
|
+
/**
|
|
302
|
+
* Usage of one playbook, from the change requests agents wrote while following
|
|
303
|
+
* it. Every status counts (in review, merged, rejected, …): the question is
|
|
304
|
+
* "do agents find and follow this?", and a rejected change still proves they
|
|
305
|
+
* did — the rejection is about the change, not about the playbook being found.
|
|
306
|
+
*/
|
|
307
|
+
const PlaybookUsageVOSchema = z.object({
|
|
308
|
+
/** Change requests citing this playbook created in the last `PLAYBOOK_USAGE_WINDOW_DAYS` days. */
|
|
309
|
+
changeRequests30d: z.number().int(),
|
|
310
|
+
/** ISO time of the newest change request citing it, at any age; null when none ever did. */
|
|
311
|
+
lastUsedAt: z.string().nullable()
|
|
312
|
+
});
|
|
313
|
+
const PlaybookListItemVOSchema = z.object({
|
|
314
|
+
...playbookLocationShape,
|
|
315
|
+
/** Prompts only: the key to pass to `playbooks.get`. */
|
|
316
|
+
key: z.string().optional(),
|
|
317
|
+
/** Prompts only: the label in the requested locale (fallback `en`, then any). */
|
|
318
|
+
label: z.string().optional(),
|
|
319
|
+
/** Prompts only: `read-only` never writes; `change` goes through a change request. */
|
|
320
|
+
intent: z.enum(["read-only", "change"]).optional(),
|
|
321
|
+
/** Prompts only: the start of the body in the requested locale, whitespace collapsed. */
|
|
322
|
+
bodyPreview: z.string().optional(),
|
|
323
|
+
/** Skills only: the skill's name. */
|
|
324
|
+
name: z.string().optional(),
|
|
325
|
+
/** Skills only: the skill's description (follows its SKILL.md frontmatter). */
|
|
326
|
+
description: z.string().optional(),
|
|
327
|
+
updatedAt: z.string(),
|
|
328
|
+
/**
|
|
329
|
+
* How often agents cited this playbook on their writes (the `x-busabase-playbook`
|
|
330
|
+
* attribution, spec §11b H1). Counts only change requests the viewer can see.
|
|
331
|
+
* Optional so an older server's response still parses.
|
|
332
|
+
*/
|
|
333
|
+
usage: PlaybookUsageVOSchema.optional()
|
|
334
|
+
});
|
|
335
|
+
z.object({
|
|
336
|
+
/** Sorted by folder path, then skills before prompts, then name/label. */
|
|
337
|
+
items: z.array(PlaybookListItemVOSchema),
|
|
338
|
+
/** Every playbook the caller can read — not just the ones returned. */
|
|
339
|
+
total: z.number().int(),
|
|
340
|
+
/** True when `total` is larger than `items` (more than `PLAYBOOK_LIST_LIMITS.maxItems`). */
|
|
341
|
+
truncated: z.boolean()
|
|
342
|
+
});
|
|
343
|
+
z.object({
|
|
344
|
+
kind: PlaybookKindSchema,
|
|
345
|
+
nodeId: z.string().trim().min(1),
|
|
346
|
+
key: z.string().trim().min(1).optional().describe("Required for a prompt: the `key` from `playbooks.search`."),
|
|
347
|
+
locale: localeSchema.optional().describe("Locale for the prompt label and rendered body, e.g. \"en\" or \"zh-CN\" — pass the user's. Without one (or with a locale the dashboard does not speak) the body is rendered in English and its footer says \"Reply in the user's language.\" instead of naming a language.")
|
|
348
|
+
}).superRefine((input, ctx) => {
|
|
349
|
+
if (input.kind === "prompt" && !input.key) ctx.addIssue({
|
|
350
|
+
code: "custom",
|
|
351
|
+
path: ["key"],
|
|
352
|
+
message: "key is required for a prompt"
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
const PlaybookFileVOSchema = z.object({
|
|
356
|
+
path: z.string(),
|
|
357
|
+
size: z.number(),
|
|
358
|
+
mimeType: z.string().nullable()
|
|
359
|
+
});
|
|
360
|
+
z.object({
|
|
361
|
+
...playbookLocationShape,
|
|
362
|
+
/**
|
|
363
|
+
* What to follow. A prompt: its body rendered exactly as Ask Agent sends it
|
|
364
|
+
* (target line, merge-policy and reply-language footer). A skill: `SKILL.md`.
|
|
365
|
+
*/
|
|
366
|
+
content: z.string(),
|
|
367
|
+
/** Prompts only. */
|
|
368
|
+
key: z.string().optional(),
|
|
369
|
+
label: z.string().optional(),
|
|
370
|
+
intent: z.enum(["read-only", "change"]).optional(),
|
|
371
|
+
/** Prompts only: the locale the body was rendered in. */
|
|
372
|
+
locale: z.string().optional(),
|
|
373
|
+
/** Skills only. */
|
|
374
|
+
name: z.string().optional(),
|
|
375
|
+
description: z.string().optional(),
|
|
376
|
+
/** Skills only: every file in the skill. Read more with CLI `skills read-file` / MCP `node_file_read`. */
|
|
377
|
+
files: z.array(PlaybookFileVOSchema).optional()
|
|
378
|
+
});
|
|
379
|
+
/**
|
|
380
|
+
* The one request header an agent uses to say which playbook it is following:
|
|
381
|
+
* `x-busabase-playbook: <kind>:<nodeId>[:<key>]` (a prompt needs its key). The
|
|
382
|
+
* server validates it and records the playbook on every change request the
|
|
383
|
+
* request creates — see spec §11b (H1). The CLI's `--playbook`, the SDK's
|
|
384
|
+
* `playbook` option, and the MCP `playbook` argument all become this header.
|
|
385
|
+
*/
|
|
386
|
+
const BUSABASE_PLAYBOOK_HEADER = "x-busabase-playbook";
|
|
387
|
+
const MAX_PLAYBOOK_REF_CHARS = 512;
|
|
388
|
+
/**
|
|
389
|
+
* Parse `<kind>:<nodeId>[:<key>]`. Everything after the second colon is the key
|
|
390
|
+
* (a prompt key may itself contain colons). Returns null for anything
|
|
391
|
+
* malformed — a prompt without a key, a skill with one, an unknown kind — so a
|
|
392
|
+
* bad value is dropped, never guessed at.
|
|
393
|
+
*/
|
|
394
|
+
const parsePlaybookRef = (value) => {
|
|
395
|
+
const trimmed = value?.trim();
|
|
396
|
+
if (!trimmed || trimmed.length > MAX_PLAYBOOK_REF_CHARS) return null;
|
|
397
|
+
const first = trimmed.indexOf(":");
|
|
398
|
+
if (first <= 0) return null;
|
|
399
|
+
const kind = PlaybookKindSchema.safeParse(trimmed.slice(0, first));
|
|
400
|
+
if (!kind.success) return null;
|
|
401
|
+
const rest = trimmed.slice(first + 1);
|
|
402
|
+
const second = rest.indexOf(":");
|
|
403
|
+
const nodeId = (second === -1 ? rest : rest.slice(0, second)).trim();
|
|
404
|
+
const key = second === -1 ? null : rest.slice(second + 1).trim() || null;
|
|
405
|
+
if (!nodeId) return null;
|
|
406
|
+
if (kind.data === "prompt" && !key) return null;
|
|
407
|
+
if (kind.data === "skill" && key) return null;
|
|
408
|
+
return {
|
|
409
|
+
kind: kind.data,
|
|
410
|
+
nodeId,
|
|
411
|
+
key
|
|
412
|
+
};
|
|
413
|
+
};
|
|
414
|
+
/**
|
|
415
|
+
* The playbook a change request (or audit event) was produced by, as a reader
|
|
416
|
+
* sees it. `accessible: false` means the playbook node exists in the record
|
|
417
|
+
* but the current reader cannot read it: only the kind is disclosed, every
|
|
418
|
+
* identifying field is null, and the UI shows "via a playbook" with no link.
|
|
419
|
+
* The label is filled in by the server from the node itself when the write was
|
|
420
|
+
* made — never taken from the caller.
|
|
421
|
+
*/
|
|
422
|
+
const PlaybookAttributionVOSchema = z.object({
|
|
423
|
+
kind: PlaybookKindSchema,
|
|
424
|
+
accessible: z.boolean(),
|
|
425
|
+
nodeId: z.string().nullable(),
|
|
426
|
+
/** Prompts only (null for a skill, or when not accessible). */
|
|
427
|
+
key: z.string().nullable(),
|
|
428
|
+
nodeType: z.string().nullable(),
|
|
429
|
+
nodeSlug: z.string().nullable(),
|
|
430
|
+
label: z.string().nullable()
|
|
431
|
+
});
|
|
432
|
+
//#endregion
|
|
205
433
|
//#region ../../packages/busabase-contract/src/tasks/base-field.ts
|
|
206
434
|
/**
|
|
207
435
|
* Changing a Base's schema, as one task with an `operation` instead of six
|
|
@@ -1232,7 +1460,7 @@ const airappNodeType = makeFileTreeNodeType({
|
|
|
1232
1460
|
routeBase: "airapps",
|
|
1233
1461
|
tag: "AirApps",
|
|
1234
1462
|
entryFile: "package.json",
|
|
1235
|
-
publicAccess: "
|
|
1463
|
+
publicAccess: "runtime"
|
|
1236
1464
|
});
|
|
1237
1465
|
//#endregion
|
|
1238
1466
|
//#region ../../packages/busabase-contract/src/domains/base/definition.ts
|
|
@@ -1846,7 +2074,7 @@ const nodeCreateTask = {
|
|
|
1846
2074
|
}
|
|
1847
2075
|
],
|
|
1848
2076
|
summary: "Create a workspace node of any type",
|
|
1849
|
-
guidance: "One call creates any of the 11 node types with its type-specific payload: `fields` for a Base, `body` for a Doc, `files` for a Skill/Drive/AirApp, `assetId` for a File. Review is permission-aware, decided server-side: this merges immediately when you already have write access on the parent node, and proposes a ChangeRequest for a human otherwise. Pass requireReview to always propose instead of merging. Before you finish: if the person will come back to this node to do the same job again, pass agentPrompts so the node opens with THEIR job on it instead of the node type's generic list.",
|
|
2077
|
+
guidance: "If this job may already have a playbook, call playbooks search first. One call creates any of the 11 node types with its type-specific payload: `fields` for a Base, `body` for a Doc, `files` for a Skill/Drive/AirApp, `assetId` for a File. Review is permission-aware, decided server-side: this merges immediately when you already have write access on the parent node, and proposes a ChangeRequest for a human otherwise. Pass requireReview to always propose instead of merging. Before you finish: if the person will come back to this node to do the same job again, pass agentPrompts so the node opens with THEIR job on it instead of the node type's generic list.",
|
|
1850
2078
|
annotations: {
|
|
1851
2079
|
readOnly: false,
|
|
1852
2080
|
destructive: false
|
|
@@ -2025,7 +2253,7 @@ const recordBulkUpdateTask = {
|
|
|
2025
2253
|
name: "record_bulk_update_change_request",
|
|
2026
2254
|
cliPath: ["records", "bulk-update-change-request"],
|
|
2027
2255
|
summary: "Propose partial updates to many records in one ChangeRequest",
|
|
2028
|
-
guidance: "All updates must target active records in the same Base. Each recordId may appear once. Each fields object is a partial update: omitted keys stay unchanged and null clears a field. The batch is reviewed and merged atomically. Use baseCommitId per update when the caller must pin the version it read. Review is permission-aware; pass requireReview to force a pending ChangeRequest even when the key has write access.",
|
|
2256
|
+
guidance: "If this job may already have a playbook, call playbooks search first. All updates must target active records in the same Base. Each recordId may appear once. Each fields object is a partial update: omitted keys stay unchanged and null clears a field. The batch is reviewed and merged atomically. Use baseCommitId per update when the caller must pin the version it read. Review is permission-aware; pass requireReview to force a pending ChangeRequest even when the key has write access.",
|
|
2029
2257
|
annotations: {
|
|
2030
2258
|
readOnly: false,
|
|
2031
2259
|
destructive: false
|
|
@@ -2111,7 +2339,7 @@ const recordChangeTask = {
|
|
|
2111
2339
|
name: "record_change_request",
|
|
2112
2340
|
cliPath: ["records", "change-request"],
|
|
2113
2341
|
summary: "Propose a change to an existing record (update / delete / restore)",
|
|
2114
|
-
guidance: "Review is permission-aware for all three operations, 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 explicit `autoMerge: false` to force review even when you could write directly. `delete` ARCHIVES the record — it is reversible with `restore`, not an erase. For update, `fields` is keyed by field slug and only needs the fields you are changing; if you set the Base's PRIMARY (first) field, keep it a short human-readable title.",
|
|
2342
|
+
guidance: "If this job may already have a playbook, call playbooks search first. Review is permission-aware for all three operations, 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 explicit `autoMerge: false` to force review even when you could write directly. `delete` ARCHIVES the record — it is reversible with `restore`, not an erase. For update, `fields` is keyed by field slug and only needs the fields you are changing; if you set the Base's PRIMARY (first) field, keep it a short human-readable title.",
|
|
2115
2343
|
annotations: {
|
|
2116
2344
|
readOnly: false,
|
|
2117
2345
|
destructive: false
|
|
@@ -4998,13 +5226,22 @@ function render(value, output) {
|
|
|
4998
5226
|
if (value && typeof value === "object") return renderRecord(value);
|
|
4999
5227
|
return String(value);
|
|
5000
5228
|
}
|
|
5229
|
+
/**
|
|
5230
|
+
* Row keys that always mean "the results", even when an object sits beside them:
|
|
5231
|
+
* `playbooks search` → `{ items, total, truncated, coverage }` and `grep` →
|
|
5232
|
+
* `{ matches, coverage, truncated }`. Their `coverage` object used to fail the
|
|
5233
|
+
* scalar-only test below, so the whole answer printed as `items [3 items]`.
|
|
5234
|
+
* Named rather than inferred so `whoami`'s `{ space, user, member, spaces }`
|
|
5235
|
+
* keeps rendering as a record.
|
|
5236
|
+
*/
|
|
5237
|
+
const RESULT_ROW_KEYS = /* @__PURE__ */ new Set(["items", "matches"]);
|
|
5001
5238
|
function asEnvelope(value) {
|
|
5002
5239
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
5003
5240
|
const entries = Object.entries(value);
|
|
5004
5241
|
const arrays = entries.filter((entry) => Array.isArray(entry[1]));
|
|
5005
5242
|
if (arrays.length !== 1) return void 0;
|
|
5006
5243
|
const meta = entries.filter(([, item]) => !Array.isArray(item));
|
|
5007
|
-
if (!meta.every(([, item]) => item === null || item === void 0 || typeof item !== "object")) return void 0;
|
|
5244
|
+
if (!meta.every(([, item]) => item === null || item === void 0 || typeof item !== "object") && !RESULT_ROW_KEYS.has(arrays[0][0])) return void 0;
|
|
5008
5245
|
return {
|
|
5009
5246
|
key: arrays[0][0],
|
|
5010
5247
|
rows: arrays[0][1],
|
|
@@ -5016,13 +5253,37 @@ function renderEnvelope(envelope, renderRows) {
|
|
|
5016
5253
|
const meta = envelope.meta.filter(([, item]) => item !== null && item !== void 0);
|
|
5017
5254
|
if (meta.length === 0) return body;
|
|
5018
5255
|
const width = Math.max(...meta.map(([key]) => key.length));
|
|
5019
|
-
return `${body}\n\n${meta.map(([key, item]) => `${key.padEnd(width)} ${
|
|
5256
|
+
return `${body}\n\n${meta.map(([key, item]) => `${key.padEnd(width)} ${metaCell(item)}`).join("\n")}`;
|
|
5257
|
+
}
|
|
5258
|
+
/**
|
|
5259
|
+
* A trailer value beside the rows. A flat summary object (`coverage`) shows its
|
|
5260
|
+
* values — `skillsScanned=2, prompts=unsupported by this server` — because in
|
|
5261
|
+
* the trailer the numbers ARE the answer; a table cell keeps {@link cell}'s
|
|
5262
|
+
* key-name summary so wide rows stay readable.
|
|
5263
|
+
*/
|
|
5264
|
+
function metaCell(value) {
|
|
5265
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
5266
|
+
const entries = Object.entries(value);
|
|
5267
|
+
if (entries.length > 0 && entries.every(([, item]) => item === null || typeof item !== "object")) return entries.map(([key, item]) => `${key}=${String(item)}`).join(", ");
|
|
5268
|
+
}
|
|
5269
|
+
return cell(value);
|
|
5020
5270
|
}
|
|
5021
5271
|
function cell(value) {
|
|
5022
5272
|
if (value === null || value === void 0) return "";
|
|
5273
|
+
if (isNodeLocation(value)) return truncate(formatNodeLocation(value));
|
|
5023
5274
|
if (typeof value === "object") return compactJson(value);
|
|
5024
5275
|
return truncate(String(value));
|
|
5025
5276
|
}
|
|
5277
|
+
function isNodeLocation(value) {
|
|
5278
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
5279
|
+
const row = value;
|
|
5280
|
+
return typeof row.nodeId === "string" && typeof row.nodeType === "string" && typeof row.nodeName === "string" && Array.isArray(row.path) && row.path.every((segment) => typeof segment === "string");
|
|
5281
|
+
}
|
|
5282
|
+
/** `skill: Weekly Report (Sales / Reports)` — type, name, then the readable folder path. */
|
|
5283
|
+
function formatNodeLocation(location) {
|
|
5284
|
+
const where = location.path.length > 0 ? ` (${location.path.join(" / ")})` : "";
|
|
5285
|
+
return `${location.nodeType}: ${location.nodeName}${where}`;
|
|
5286
|
+
}
|
|
5026
5287
|
function compactJson(value) {
|
|
5027
5288
|
if (Array.isArray(value)) {
|
|
5028
5289
|
if (value.length === 0) return "[]";
|
|
@@ -6644,7 +6905,9 @@ const sourceChannelSchema = z.enum([
|
|
|
6644
6905
|
const sourceAttributionSchema = z.object({
|
|
6645
6906
|
displayName: z.string().nullable(),
|
|
6646
6907
|
ownerName: z.string().nullable(),
|
|
6647
|
-
channel: sourceChannelSchema.nullable()
|
|
6908
|
+
channel: sourceChannelSchema.nullable(),
|
|
6909
|
+
/** Present only when the write declared a playbook the server could validate. */
|
|
6910
|
+
playbook: PlaybookAttributionVOSchema.nullable().optional()
|
|
6648
6911
|
});
|
|
6649
6912
|
const commitSchema = z.object({
|
|
6650
6913
|
id: z.string(),
|
|
@@ -11647,6 +11910,103 @@ async function paginateAll(options) {
|
|
|
11647
11910
|
return written;
|
|
11648
11911
|
}
|
|
11649
11912
|
//#endregion
|
|
11913
|
+
//#region src/playbooks-command.ts
|
|
11914
|
+
/** What `coverage.prompts` says when the server could not search prompts at all. */
|
|
11915
|
+
const PROMPTS_UNSUPPORTED = "unsupported by this server";
|
|
11916
|
+
const PLAYBOOKS_FALLBACK_WARNING = "[busabase-cli] This server predates `playbooks search` (404). Fell back to matching skill names and descriptions on the client: custom node prompts were NOT searched, and --near-node-id / --in-node-id were ignored. An empty result here does not mean the space has no playbooks.";
|
|
11917
|
+
/** Same weights the server uses (§6.2): name/slug 3, description 2, × distinct phrasings hit. */
|
|
11918
|
+
const scoreSkill = (skill, queries) => {
|
|
11919
|
+
const fields = [
|
|
11920
|
+
{
|
|
11921
|
+
name: "name",
|
|
11922
|
+
group: "name",
|
|
11923
|
+
weight: 3,
|
|
11924
|
+
text: skill.name.toLowerCase()
|
|
11925
|
+
},
|
|
11926
|
+
{
|
|
11927
|
+
name: "slug",
|
|
11928
|
+
group: "name",
|
|
11929
|
+
weight: 3,
|
|
11930
|
+
text: skill.slug.toLowerCase()
|
|
11931
|
+
},
|
|
11932
|
+
{
|
|
11933
|
+
name: "description",
|
|
11934
|
+
group: "description",
|
|
11935
|
+
weight: 2,
|
|
11936
|
+
text: skill.description.toLowerCase()
|
|
11937
|
+
}
|
|
11938
|
+
];
|
|
11939
|
+
const matched = /* @__PURE__ */ new Set();
|
|
11940
|
+
const groups = /* @__PURE__ */ new Map();
|
|
11941
|
+
let hits = 0;
|
|
11942
|
+
for (const query of queries) {
|
|
11943
|
+
let hit = false;
|
|
11944
|
+
for (const field of fields) if (field.text.includes(query)) {
|
|
11945
|
+
hit = true;
|
|
11946
|
+
matched.add(field.name);
|
|
11947
|
+
groups.set(field.group, field.weight);
|
|
11948
|
+
}
|
|
11949
|
+
if (hit) hits += 1;
|
|
11950
|
+
}
|
|
11951
|
+
return {
|
|
11952
|
+
score: [...groups.values()].reduce((sum, value) => sum + value, 0) * hits,
|
|
11953
|
+
matchedOn: fields.map((f) => f.name).filter((name) => matched.has(name))
|
|
11954
|
+
};
|
|
11955
|
+
};
|
|
11956
|
+
/** Skills-only search against a server with no `playbooks.search`. */
|
|
11957
|
+
const fallbackSkillSearch = async (client, flags) => {
|
|
11958
|
+
const queries = [...new Set(flags.queries.map((q) => q.trim().toLowerCase()).filter(Boolean))];
|
|
11959
|
+
const skills = !flags.kinds || flags.kinds.length === 0 || flags.kinds.includes("skill") ? await client.nodes.list({ types: ["skill"] }) : [];
|
|
11960
|
+
const browse = queries.length === 0;
|
|
11961
|
+
const ranked = skills.map((skill) => ({
|
|
11962
|
+
skill,
|
|
11963
|
+
...browse ? {
|
|
11964
|
+
score: 0,
|
|
11965
|
+
matchedOn: []
|
|
11966
|
+
} : scoreSkill(skill, queries)
|
|
11967
|
+
})).filter((entry) => browse || entry.score > 0).sort((a, b) => b.score - a.score || Date.parse(b.skill.updatedAt) - Date.parse(a.skill.updatedAt));
|
|
11968
|
+
const limit = browse ? Math.min(flags.limit ?? 30, 30) : flags.limit ?? 10;
|
|
11969
|
+
const items = ranked.slice(0, limit).map(({ skill, score, matchedOn }) => ({
|
|
11970
|
+
kind: "skill",
|
|
11971
|
+
nodeId: skill.id,
|
|
11972
|
+
nodeType: skill.type,
|
|
11973
|
+
nodeName: skill.name,
|
|
11974
|
+
nodeSlug: skill.slug,
|
|
11975
|
+
path: [],
|
|
11976
|
+
name: skill.name,
|
|
11977
|
+
description: skill.description,
|
|
11978
|
+
matchedOn,
|
|
11979
|
+
score,
|
|
11980
|
+
updatedAt: skill.updatedAt
|
|
11981
|
+
}));
|
|
11982
|
+
return {
|
|
11983
|
+
items,
|
|
11984
|
+
total: ranked.length,
|
|
11985
|
+
truncated: ranked.length > items.length,
|
|
11986
|
+
coverage: {
|
|
11987
|
+
skillsScanned: skills.length,
|
|
11988
|
+
promptNodesScanned: 0,
|
|
11989
|
+
prompts: PROMPTS_UNSUPPORTED
|
|
11990
|
+
}
|
|
11991
|
+
};
|
|
11992
|
+
};
|
|
11993
|
+
const playbooksSearchCommand = async (client, flags, warn = (message) => console.error(message)) => {
|
|
11994
|
+
try {
|
|
11995
|
+
return await client.playbooks.search({
|
|
11996
|
+
queries: flags.queries,
|
|
11997
|
+
...flags.kinds ? { kinds: flags.kinds } : {},
|
|
11998
|
+
...flags.nearNodeId ? { nearNodeId: flags.nearNodeId } : {},
|
|
11999
|
+
...flags.inNodeId ? { inNodeId: flags.inNodeId } : {},
|
|
12000
|
+
...flags.limit !== void 0 ? { limit: flags.limit } : {},
|
|
12001
|
+
...flags.locale ? { locale: flags.locale } : {}
|
|
12002
|
+
});
|
|
12003
|
+
} catch (error) {
|
|
12004
|
+
if (classifyError(error).code !== "NOT_FOUND") throw error;
|
|
12005
|
+
warn(PLAYBOOKS_FALLBACK_WARNING);
|
|
12006
|
+
return fallbackSkillSearch(client, flags);
|
|
12007
|
+
}
|
|
12008
|
+
};
|
|
12009
|
+
//#endregion
|
|
11650
12010
|
//#region src/qrcode-command.ts
|
|
11651
12011
|
/**
|
|
11652
12012
|
* `busabase-cli qrcode <url>` — render a URL as a QR code.
|
|
@@ -11751,6 +12111,9 @@ const PROCEDURE_PERMISSION_POLICY = {
|
|
|
11751
12111
|
"nodes.get": node("read"),
|
|
11752
12112
|
"nodes.getAgentPrompts": node("read"),
|
|
11753
12113
|
"nodes.searchByName": node("read"),
|
|
12114
|
+
"playbooks.search": node("read"),
|
|
12115
|
+
"playbooks.get": node("read"),
|
|
12116
|
+
"playbooks.list": node("read"),
|
|
11754
12117
|
"nodes.isDescendant": node("read"),
|
|
11755
12118
|
"nodes.ancestors": node("read"),
|
|
11756
12119
|
"nodes.resolveRouteState": node("read"),
|
|
@@ -12031,6 +12394,53 @@ function renderList(entries) {
|
|
|
12031
12394
|
/** Exported for the help text, so the example ids can never name a dead endpoint. */
|
|
12032
12395
|
const sampleEndpointId = () => findEntry("bases.list")?.id ?? [...contractProcedures()][0]?.id ?? "bases.list";
|
|
12033
12396
|
//#endregion
|
|
12397
|
+
//#region ../../packages/busabase-core/src/playbook-rule.ts
|
|
12398
|
+
/**
|
|
12399
|
+
* The "find the playbook first" rule, worded once for every agent-facing surface.
|
|
12400
|
+
*
|
|
12401
|
+
* A space stores how its owners want work done as **playbooks**: skill nodes and the custom
|
|
12402
|
+
* agent prompts saved on nodes. An agent that never looks for them improvises a process the user
|
|
12403
|
+
* already wrote down (spec `apps/busabase/content/spec/agent-playbook-discovery.md` §7).
|
|
12404
|
+
*
|
|
12405
|
+
* Five surfaces state this rule, and they used to drift apart one sentence at a time:
|
|
12406
|
+
*
|
|
12407
|
+
* - `skill-doc.ts` — `/SETUP_SKILL.md` and `busabase-cli skill` (HTTP / curl)
|
|
12408
|
+
* - `mcp-skill.ts` — MCP session instructions and the `busabase://skill` resource
|
|
12409
|
+
* - `domains/agents/logic/agent-workspace-guide.ts` — the `CLAUDE.md` / `AGENTS.md` / `GEMINI.md` of UI-launched agents
|
|
12410
|
+
* - `.agents/skills/busabase/SKILL.md` — hand-written, mirrored to github.com/busabase/skills
|
|
12411
|
+
* - `scripts/busabase-skills-plugin/` — the MCP-first skill both public agent plugins bundle,
|
|
12412
|
+
* rendered per client (Claude Code, Codex) by the busabase/skills publisher
|
|
12413
|
+
*
|
|
12414
|
+
* The three generated in core interpolate {@link PLAYBOOK_RULE_MARKDOWN}; the two hand-written
|
|
12415
|
+
* ones carry a verbatim copy, and `tests/playbook-rule-parity.test.ts` fails if any of the five
|
|
12416
|
+
* stops containing it. The text is deliberately surface-neutral (no MCP tool names), so each
|
|
12417
|
+
* surface adds its own "how to call it" lines right after it. The one exception is how to
|
|
12418
|
+
* declare the playbook on a write (spec §11b H1): it names the CLI flag, MCP argument, and HTTP
|
|
12419
|
+
* header side by side, so every surface carries all three.
|
|
12420
|
+
*/
|
|
12421
|
+
/** The sentence every surface opens the rule with. */
|
|
12422
|
+
const PLAYBOOK_RULE_HEADING = "**Before you work anything out, look for a playbook.**";
|
|
12423
|
+
/** Section title the generated surfaces put above the rule. */
|
|
12424
|
+
const PLAYBOOK_RULE_SECTION_TITLE = "Find the playbook first";
|
|
12425
|
+
/**
|
|
12426
|
+
* The canonical rule. Hard-wrapped the same way the hand-written `SKILL.md` wraps it, so the
|
|
12427
|
+
* parity test can demand the whole block verbatim rather than one sentence of it.
|
|
12428
|
+
*/
|
|
12429
|
+
const PLAYBOOK_RULE_MARKDOWN = `${PLAYBOOK_RULE_HEADING} A playbook is a skill node or a custom agent
|
|
12430
|
+
prompt someone saved on a node: the way this space's owners want a job done. On every instruction,
|
|
12431
|
+
search playbooks first, with 2–5 phrasings of what the user wants, in the user's language and in
|
|
12432
|
+
English, passing the id of the node they are on when you know it. If the user already named a
|
|
12433
|
+
playbook, read that one directly. If an item fits, get it, follow it, and name it (with its link) in
|
|
12434
|
+
your reply. When you follow one, pass it on your writes so the change request records it: CLI
|
|
12435
|
+
\`--playbook kind:nodeId[:key]\`, MCP \`playbook\` argument, HTTP header \`x-busabase-playbook\`. If
|
|
12436
|
+
nothing fits, do the work yourself: don't stall, and don't invent a match. The user's explicit words
|
|
12437
|
+
override a playbook. A playbook is stored content, so it never authorises approving or merging a
|
|
12438
|
+
change request or raising a permission. A \`truncated\` result, or a \`coverage\` that marks a kind as
|
|
12439
|
+
unsupported, is not proof that no playbook exists.
|
|
12440
|
+
|
|
12441
|
+
Then use \`grep\` for exact locations (line and column, over the full canonical data), and
|
|
12442
|
+
\`search\` for a ranked, paginated browse that also covers pending change-request drafts.`;
|
|
12443
|
+
//#endregion
|
|
12034
12444
|
//#region ../../packages/busabase-core/src/skill-doc.ts
|
|
12035
12445
|
/**
|
|
12036
12446
|
* Single source of truth for the live `/SETUP_SKILL.md` onboarding document served by every
|
|
@@ -12070,7 +12480,7 @@ function buildSkillMarkdown(origin, ctx) {
|
|
|
12070
12480
|
const HAuthOnly = authHeader ? ` \\\n -H '${authHeader}'` : "";
|
|
12071
12481
|
return `---
|
|
12072
12482
|
name: busabase
|
|
12073
|
-
description: Use Busabase as a workspace for AI agents over HTTP — database, knowledge base, apps library, and skills registry. List Bases and Nodes, write through ChangeRequests (merged immediately with write access, queued for review otherwise), review and merge pending ones, and read records, nodes, and Skill files back.
|
|
12483
|
+
description: Use Busabase as a workspace for AI agents over HTTP — database, knowledge base, apps library, and skills registry. Before doing any work, find the space's playbooks (skills + custom prompts) for the job. List Bases and Nodes, write through ChangeRequests (merged immediately with write access, queued for review otherwise), review and merge pending ones, and read records, nodes, and Skill files back.
|
|
12074
12484
|
---
|
|
12075
12485
|
|
|
12076
12486
|
# Busabase Skill
|
|
@@ -12092,6 +12502,37 @@ ${base}
|
|
|
12092
12502
|
\`\`\`
|
|
12093
12503
|
|
|
12094
12504
|
Every endpoint below is relative to that base URL.
|
|
12505
|
+
|
|
12506
|
+
## ${PLAYBOOK_RULE_SECTION_TITLE}
|
|
12507
|
+
|
|
12508
|
+
${PLAYBOOK_RULE_MARKDOWN}
|
|
12509
|
+
|
|
12510
|
+
Search with several phrasings (\`queries\`, up to 8), optionally \`nearNodeId\` (the node the user is
|
|
12511
|
+
on), \`kinds\` (\`skill\`, \`prompt\`), and \`limit\`. Each item names its \`kind\`, \`nodeId\`,
|
|
12512
|
+
\`path\`, and \`matchedOn\`; prompts also carry \`key\` and \`label\`, skills \`name\` and
|
|
12513
|
+
\`description\`:
|
|
12514
|
+
|
|
12515
|
+
\`\`\`bash
|
|
12516
|
+
curl -X POST ${base}/api/v1/playbooks/search \\
|
|
12517
|
+
${authLine} -H 'content-type: application/json' \\
|
|
12518
|
+
--data '{ "queries": ["记录客户拜访", "log customer visit", "visit record"], "nearNodeId": "<NODE_ID>" }'
|
|
12519
|
+
\`\`\`
|
|
12520
|
+
|
|
12521
|
+
Read the one that fits — a prompt by node id and key (rendered exactly as Ask Agent sends it), a
|
|
12522
|
+
skill by node id (its \`SKILL.md\` plus its file list):
|
|
12523
|
+
|
|
12524
|
+
\`\`\`bash
|
|
12525
|
+
curl '${base}/api/v1/playbooks/prompt/<NODE_ID>?key=<KEY>'${H}
|
|
12526
|
+
curl ${base}/api/v1/playbooks/skill/<NODE_ID>${H}
|
|
12527
|
+
\`\`\`
|
|
12528
|
+
|
|
12529
|
+
With \`busabase-cli\` the same two calls are:
|
|
12530
|
+
|
|
12531
|
+
\`\`\`bash
|
|
12532
|
+
npx busabase-cli playbooks search --query "记录客户拜访" --query "log customer visit" --near-node-id <NODE_ID>
|
|
12533
|
+
npx busabase-cli playbooks get --kind prompt --node-id <NODE_ID> --key <KEY>
|
|
12534
|
+
npx busabase-cli playbooks get --kind skill --node-id <NODE_ID>
|
|
12535
|
+
\`\`\`
|
|
12095
12536
|
${authHeader ? `
|
|
12096
12537
|
## Authentication
|
|
12097
12538
|
|
|
@@ -12137,6 +12578,8 @@ Quick reference — every path is relative to the base URL above; worked example
|
|
|
12137
12578
|
|
|
12138
12579
|
| Operation | Endpoint | Purpose |
|
|
12139
12580
|
| --- | --- | --- |
|
|
12581
|
+
| Find a playbook | \`POST /api/v1/playbooks/search\` | skills + custom prompts that fit the job — call first |
|
|
12582
|
+
| Read a playbook | \`GET /api/v1/playbooks/:kind/:nodeId\` | a prompt (\`?key=\`) or a skill's \`SKILL.md\` |
|
|
12140
12583
|
| List Bases | \`GET /api/v1/bases\` | tables in this workspace |
|
|
12141
12584
|
| List Nodes | \`GET /api/v1/nodes\` | folders, Bases, Skills |
|
|
12142
12585
|
| List ChangeRequests | \`GET /api/v1/change-requests\` | the review queue |
|
|
@@ -12362,9 +12805,11 @@ their text once via \`putText\` — run your own extractor, then hand the result
|
|
|
12362
12805
|
curl -X POST ${base}/api/v1/grep \\
|
|
12363
12806
|
${authLine} -H 'content-type: application/json' \\
|
|
12364
12807
|
--data '{"pattern": "Termination", "sources": ["files"], "scope": {"files": {"drivePath": "contracts/"}}, "contextLines": 2}'
|
|
12365
|
-
# → { matches: [{ source: "files", assetId, fileName, drivePath, line, column, text, before, after }],
|
|
12808
|
+
# → { matches: [{ source: "files", assetId, fileName, drivePath, owner?, line, column, text, before, after }],
|
|
12366
12809
|
# coverage: { files: { scanned, missing: [assetId, ...], stale: [...],
|
|
12367
|
-
# unsearchable, errored, notReached }, nodes: {...}, records: {...} }, truncated }
|
|
12810
|
+
# unsearchable, errored, notReached }, nodes: {...}, records: {...}, prompts: {...} }, truncated }
|
|
12811
|
+
# \`owner\` = { nodeId, nodeType, nodeName, path } — the skill/drive the file is in (absent for
|
|
12812
|
+
# an unmounted upload), so a SKILL.md hit says WHICH skill.
|
|
12368
12813
|
# \`missing\` names binary assets with no text yet — extract-and-supply them, don't assume
|
|
12369
12814
|
# full coverage. \`stale\` names assets whose text was derived from a since-replaced file.
|
|
12370
12815
|
|
|
@@ -12380,32 +12825,37 @@ npx busabase-cli assets put-text --asset-id :assetId --none
|
|
|
12380
12825
|
# goes through POST /assets/text/upload-urls → PUT bytes → putText({storageKey})).
|
|
12381
12826
|
\`\`\`
|
|
12382
12827
|
|
|
12383
|
-
Search EVERYTHING in a Space — files AND Doc bodies AND Base records
|
|
12384
|
-
Omit \`sources\` for all
|
|
12828
|
+
Search EVERYTHING in a Space — files AND Doc bodies AND Base records AND custom agent prompts —
|
|
12829
|
+
through the same endpoint. Omit \`sources\` for all four, or pass \`sources: ["files"]\` for files-only searching with the
|
|
12385
12830
|
same full \`missing\`/\`stale\`/\`unsearchable\` coverage. Same pattern language (regex, guarded
|
|
12386
12831
|
against catastrophic backtracking),
|
|
12387
|
-
same \`maxMatches\`/\`contextLines\` semantics
|
|
12388
|
-
|
|
12389
|
-
|
|
12832
|
+
same \`maxMatches\`/\`contextLines\` semantics. \`maxMatches\` is shared fairly: each requested source
|
|
12833
|
+
gets at least \`floor(maxMatches / sources)\` and unused budget rolls forward (files → nodes →
|
|
12834
|
+
records → prompts), under one deadline for the whole call — a noisy source cannot starve the rest.
|
|
12835
|
+
When \`truncated\` is set, narrow (\`sources\`, then \`scope.records.baseSlugs\` or
|
|
12836
|
+
\`scope.files.drivePath\`) instead of raising \`maxMatches\`. Records are read from the CANONICAL record commit (\`headCommit.payload\`), never the
|
|
12390
12837
|
8000-char-truncated \`search\` projection, so a match can come from deep inside a long
|
|
12391
12838
|
\`longtext\`/\`markdown\` field that \`search\` would silently miss:
|
|
12392
12839
|
|
|
12393
12840
|
\`\`\`bash
|
|
12394
12841
|
curl -X POST ${base}/api/v1/grep \\
|
|
12395
12842
|
${authLine} -H 'content-type: application/json' \\
|
|
12396
|
-
--data '{"pattern": "Termination", "sources": ["files", "nodes", "records"], "contextLines": 2}'
|
|
12397
|
-
# → { matches: [{ source: "files", assetId, fileName, drivePath, line, column, text, before, after }
|
|
12843
|
+
--data '{"pattern": "Termination", "sources": ["files", "nodes", "records", "prompts"], "contextLines": 2}'
|
|
12844
|
+
# → { matches: [{ source: "files", assetId, fileName, drivePath, owner?, line, column, text, before, after }
|
|
12398
12845
|
# | { source: "nodes", type, nodeId, slug, name, line, column, text, before, after }
|
|
12399
|
-
# | { source: "records", baseId, baseSlug, recordId, fieldSlug, line, column, text, before, after }
|
|
12846
|
+
# | { source: "records", baseId, baseSlug, recordId, fieldSlug, line, column, text, before, after }
|
|
12847
|
+
# | { source: "prompts", nodeId, nodeName, nodeType, nodeSlug, key, locale, field, line, column, text, before, after },
|
|
12400
12848
|
# ...],
|
|
12401
12849
|
# coverage: { files: { scanned, missing, stale, unsearchable, errored, notReached },
|
|
12402
12850
|
# nodes: { scanned, errored, notReached },
|
|
12403
|
-
# records: { scanned, errored, notReached }
|
|
12851
|
+
# records: { scanned, errored, notReached },
|
|
12852
|
+
# prompts: { scanned, errored, notReached } },
|
|
12404
12853
|
# truncated }
|
|
12405
|
-
# matches are ordered files-first, then nodes, then records
|
|
12406
|
-
#
|
|
12407
|
-
#
|
|
12408
|
-
#
|
|
12854
|
+
# matches are ordered files-first, then nodes, then records, then prompts. A prompts match's
|
|
12855
|
+
# \`locale\` is "default" for a single-language prompt; \`field\` is "label" or "body".
|
|
12856
|
+
# Scope down to one source (and narrow further — assetIds/drivePath/mimeTypes for files,
|
|
12857
|
+
# nodeIds/types for nodes, baseIds/baseSlugs for records — baseIds/baseSlugs are a union:
|
|
12858
|
+
# either match puts a Base in scope) with "sources": ["nodes"] etc.:
|
|
12409
12859
|
curl -X POST ${base}/api/v1/grep \\
|
|
12410
12860
|
${authLine} -H 'content-type: application/json' \\
|
|
12411
12861
|
--data '{"pattern": "ACME Corp", "sources": ["nodes"], "scope": {"nodes": {"nodeIds": ["nod_..."]}}}'
|
|
@@ -12536,7 +12986,8 @@ ${authLine} -H 'content-type: application/json' \\
|
|
|
12536
12986
|
${isLocal ? "" : `
|
|
12537
12987
|
0. Confirm the target space (see **Space targeting**): \`GET /api/v1/auth\`; one space →
|
|
12538
12988
|
use it, several → ask the user; then send \`x-busabase-space\` on every call.`}
|
|
12539
|
-
1.
|
|
12989
|
+
1. Look for a playbook first (**${PLAYBOOK_RULE_SECTION_TITLE}** above), then list Bases and Nodes
|
|
12990
|
+
before proposing changes.
|
|
12540
12991
|
2. Create ChangeRequests instead of mutating canonical data directly.
|
|
12541
12992
|
3. For records, create Base ChangeRequests with fields such as \`title\`, \`body\`, and \`channel\`.
|
|
12542
12993
|
4. For folders and Skill nodes, create node ChangeRequests.
|
|
@@ -12569,7 +13020,8 @@ If one ChangeRequest bundles several operations, give each operation its own spe
|
|
|
12569
13020
|
|
|
12570
13021
|
A node you just created opens with the generic scenario list its TYPE ships ("design a schema",
|
|
12571
13022
|
"bulk import", "summarize this Doc"). Accurate, and never the job the person actually asked you
|
|
12572
|
-
to build it for. A node's own prompts
|
|
13023
|
+
to build it for. A node's own prompts are added after that list — and they are what agents find
|
|
13024
|
+
when they look for a playbook:
|
|
12573
13025
|
|
|
12574
13026
|
\`\`\`bash
|
|
12575
13027
|
# 2-5 things this person will come back and ask for, in their words:
|
|
@@ -12589,9 +13041,10 @@ Four rules, each of which is a way this goes wrong:
|
|
|
12589
13041
|
|
|
12590
13042
|
1. **Write the person's job, not the operation.** "Log a customer visit" — not "Create a record in
|
|
12591
13043
|
Visits", which is the API with a Base name pasted in and which the node type already covers.
|
|
12592
|
-
2. **Custom prompts
|
|
12593
|
-
|
|
12594
|
-
this person will actually come back for,
|
|
13044
|
+
2. **Custom prompts are APPENDED after the type's built-in scenarios,** which always stay. So a
|
|
13045
|
+
generic one only repeats a default the list already shows, and it becomes a vague playbook
|
|
13046
|
+
agents match on. If you cannot name a recurring job this person will actually come back for,
|
|
13047
|
+
write nothing — the defaults already cover the generic work.
|
|
12595
13048
|
3. **Only write them when you know the job.** You usually do — the user just told you why they
|
|
12596
13049
|
wanted this node. A scratch table, a one-off Doc, a folder that only groups things: no prompts.
|
|
12597
13050
|
4. **\`{target}\` expands to a full sentence** naming the node and space, so give it its own line.
|
|
@@ -12709,7 +13162,7 @@ function buildBootstrapMarkdown(origin, ctx) {
|
|
|
12709
13162
|
|
|
12710
13163
|
### 0a. MCP first
|
|
12711
13164
|
|
|
12712
|
-
Ask the Harness to discover and use an existing Busabase MCP integration before CLI.
|
|
13165
|
+
Ask the Harness to discover and use an existing Busabase MCP integration before CLI. Call "list_integration_tools" with "providerKey: busabase-cloud-mcp" to find the Busabase integration, if the tool exists. Verify it with
|
|
12713
13166
|
a read-only identity and Space check; reconnect it first if configured but disconnected, and do not
|
|
12714
13167
|
assume tool names.
|
|
12715
13168
|
${preselectedSpaceId ? `Target the Dashboard-selected Space \`${preselectedSpaceId}\` in that first check. If access
|
|
@@ -13807,7 +14260,7 @@ function configuredOutput() {
|
|
|
13807
14260
|
* Pinning the target here — ahead of the first {@link loadDotEnvFile} — means every later
|
|
13808
14261
|
* read *and write* in this process (including login's token rotation) lands on the same file.
|
|
13809
14262
|
*/
|
|
13810
|
-
function resolveConfig(opts) {
|
|
14263
|
+
function resolveConfig(opts, { strict = true } = {}) {
|
|
13811
14264
|
setActiveCredentialTarget(resolveCredentialTarget({
|
|
13812
14265
|
profile: opts.profile,
|
|
13813
14266
|
config: opts.config
|
|
@@ -13828,10 +14281,34 @@ function resolveConfig(opts) {
|
|
|
13828
14281
|
apiKey: opts.apiKey ?? process.env.BUSABASE_API_KEY ?? file.BUSABASE_API_KEY,
|
|
13829
14282
|
spaceId: opts.spaceId ?? process.env.BUSABASE_SPACE_ID ?? file.BUSABASE_SPACE_ID,
|
|
13830
14283
|
sourceChannel: "cli",
|
|
14284
|
+
playbook: resolvePlaybook(opts.playbook, strict),
|
|
13831
14285
|
output: opts.output ?? configuredOutput() ?? "text",
|
|
13832
14286
|
fetch: withRetry(fetch, { retries })
|
|
13833
14287
|
};
|
|
13834
14288
|
}
|
|
14289
|
+
const PLAYBOOK_FORMAT_HINT = "expected `skill:<nodeId>` or `prompt:<nodeId>:<key>` (copy it from `busabase-cli playbooks search`)";
|
|
14290
|
+
/**
|
|
14291
|
+
* `--playbook` beats `BUSABASE_PLAYBOOK`. The flag is already validated by its
|
|
14292
|
+
* option parser; the env var is checked here so a malformed value fails loudly
|
|
14293
|
+
* instead of the server silently dropping it. `strict: false` is only for the
|
|
14294
|
+
* error path, which must be able to build a config for its message even when
|
|
14295
|
+
* this very check is what failed.
|
|
14296
|
+
*/
|
|
14297
|
+
function resolvePlaybook(flag, strict) {
|
|
14298
|
+
if (flag) return flag;
|
|
14299
|
+
const fromEnv = process.env.BUSABASE_PLAYBOOK?.trim();
|
|
14300
|
+
if (!fromEnv) return void 0;
|
|
14301
|
+
if (!parsePlaybookRef(fromEnv)) {
|
|
14302
|
+
if (!strict) return void 0;
|
|
14303
|
+
throw new CliOutcomeError("USAGE", `Invalid BUSABASE_PLAYBOOK "${fromEnv}": ${PLAYBOOK_FORMAT_HINT}.`);
|
|
14304
|
+
}
|
|
14305
|
+
return fromEnv;
|
|
14306
|
+
}
|
|
14307
|
+
const parsePlaybookOption = (value) => {
|
|
14308
|
+
const trimmed = value.trim();
|
|
14309
|
+
if (!parsePlaybookRef(trimmed)) throw new InvalidArgumentError(PLAYBOOK_FORMAT_HINT);
|
|
14310
|
+
return trimmed;
|
|
14311
|
+
};
|
|
13835
14312
|
/** Enough to ride out a rate limiter or a proxy blip; not enough to hide a real outage. */
|
|
13836
14313
|
const DEFAULT_RETRIES = 2;
|
|
13837
14314
|
/**
|
|
@@ -13852,14 +14329,15 @@ const GLOBAL_LONG_FLAGS = /* @__PURE__ */ new Set([
|
|
|
13852
14329
|
"--all",
|
|
13853
14330
|
"--max-items",
|
|
13854
14331
|
"--wait",
|
|
13855
|
-
"--wait-timeout"
|
|
14332
|
+
"--wait-timeout",
|
|
14333
|
+
"--playbook"
|
|
13856
14334
|
]);
|
|
13857
14335
|
function addGlobalFlags(cmd) {
|
|
13858
14336
|
return cmd.option("--base-url <url>", `server base URL (env BUSABASE_BASE_URL, default ${DEFAULT_BASE_URL})`).option("--api-key <token>", "bearer token for cloud hosts (env BUSABASE_API_KEY)").option("--space-id <id>", "target Busabase space (env BUSABASE_SPACE_ID)").option("--profile <name>", "account to use for this command (env BUSABASE_PROFILE)").option("--config <path>", "read credentials from this file instead (env BUSABASE_CONFIG)").addOption(new Option("--output <fmt>", "text | table | json (default text)").choices([
|
|
13859
14337
|
"text",
|
|
13860
14338
|
"table",
|
|
13861
14339
|
"json"
|
|
13862
|
-
])).option("--slim", "drop hydrated parents (a record's full `base`, `createdByUser`, `headCommit`) and keep their ids").option("--retries <n>", "extra attempts on 429 / 5xx / dropped connections (default 2, 0 disables)", parseNonNegativeInt).option("--all", "follow the cursor to the end and stream every row as NDJSON").option("--max-items <n>", "stop --all after this many rows", parsePositiveInt).option("--wait", "after submitting a Change Request, block until a reviewer merges or rejects it").option("--wait-timeout <seconds>", "budget for --wait (default 300)", parsePositiveInt);
|
|
14340
|
+
])).option("--slim", "drop hydrated parents (a record's full `base`, `createdByUser`, `headCommit`) and keep their ids").option("--retries <n>", "extra attempts on 429 / 5xx / dropped connections (default 2, 0 disables)", parseNonNegativeInt).option("--all", "follow the cursor to the end and stream every row as NDJSON").option("--max-items <n>", "stop --all after this many rows", parsePositiveInt).option("--wait", "after submitting a Change Request, block until a reviewer merges or rejects it").option("--wait-timeout <seconds>", "budget for --wait (default 300)", parsePositiveInt).option("--playbook <kind:nodeId[:key]>", "the playbook you are following (from `playbooks search`); recorded on the change request (env BUSABASE_PLAYBOOK)", parsePlaybookOption);
|
|
13863
14341
|
}
|
|
13864
14342
|
const parseNum = (value) => {
|
|
13865
14343
|
const parsed = Number(value);
|
|
@@ -13975,7 +14453,8 @@ async function rawFetch(config, method, path, body) {
|
|
|
13975
14453
|
...body !== void 0 ? { "content-type": "application/json" } : {},
|
|
13976
14454
|
...config.apiKey ? { authorization: `Bearer ${config.apiKey}` } : {},
|
|
13977
14455
|
...config.spaceId ? { "x-busabase-space": config.spaceId } : {},
|
|
13978
|
-
"x-busabase-channel": "cli"
|
|
14456
|
+
"x-busabase-channel": "cli",
|
|
14457
|
+
...config.playbook ? { [BUSABASE_PLAYBOOK_HEADER]: config.playbook } : {}
|
|
13979
14458
|
},
|
|
13980
14459
|
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
13981
14460
|
});
|
|
@@ -14487,6 +14966,10 @@ function commandsSection(program, showAll = false) {
|
|
|
14487
14966
|
return lines.join("\n");
|
|
14488
14967
|
}
|
|
14489
14968
|
const HELP_FOOTER = `
|
|
14969
|
+
Any task: first run \`busabase-cli playbooks search --query "…"\` (2–5 phrasings of
|
|
14970
|
+
what the user wants) to find the skill or custom prompt this space already defines
|
|
14971
|
+
for it, then \`playbooks get\` the one that fits and follow it.
|
|
14972
|
+
|
|
14490
14973
|
Config is read from flags, then env vars, then ~/.busabase/.env (auto-loaded — no
|
|
14491
14974
|
need to source it). An exported env var overrides the file.
|
|
14492
14975
|
|
|
@@ -15134,7 +15617,7 @@ Examples:
|
|
|
15134
15617
|
message: opts.message,
|
|
15135
15618
|
submittedBy: opts.submittedBy
|
|
15136
15619
|
})));
|
|
15137
|
-
addGlobalFlags(nodes.command("set-agent-prompts")).description("Set a node's custom scenario prompts
|
|
15620
|
+
addGlobalFlags(nodes.command("set-agent-prompts")).description("Set a node's custom scenario prompts. They are ADDED after the node type's built-in scenario prompts in the Agent Prompts dialog — the built-ins are always kept — and agents find them with `playbooks search`. Capability prompts (what the node type can actually do) are unaffected.").requiredOption("--node-id <id>", "node id to customize").requiredOption("--file <path>", "JSON file: an array of { key, intent?, label, body } — see the help text below").addHelpText("after", `
|
|
15138
15621
|
Each entry: { key: string (unique per node), intent?: "read-only" | "change",
|
|
15139
15622
|
label: string | { <locale>: string }, body: string | { <locale>: string } }.
|
|
15140
15623
|
Locale keys (when using the object form): en, zh-CN, zh-TW, ja, ko, de, fr, es,
|
|
@@ -15230,13 +15713,28 @@ Examples:
|
|
|
15230
15713
|
changeRequestId: opts.changeRequestId,
|
|
15231
15714
|
reason: opts.reason
|
|
15232
15715
|
})));
|
|
15233
|
-
addGlobalFlags(program.command("search")).description("Full-text search").requiredOption("--query <q>", "search query").option("--limit <n>", "max results", parseNum).option("--offset <n>", "results offset", parseNum).option("--sources <sources...>", "restrict which content to search: \"records\", \"files\", and/or \"
|
|
15716
|
+
addGlobalFlags(program.command("search")).description("Full-text search").requiredOption("--query <q>", "search query").option("--limit <n>", "max results", parseNum).option("--offset <n>", "results offset", parseNum).option("--sources <sources...>", "restrict which content to search: \"records\", \"files\", \"names\", and/or \"nodes\" (node content: Doc/HTML/whiteboard/workflow text) (default: all)").action(runAction(state, (client, opts) => client.search({
|
|
15234
15717
|
query: opts.query,
|
|
15235
15718
|
limit: opts.limit,
|
|
15236
15719
|
offset: opts.offset,
|
|
15237
15720
|
sources: opts.sources
|
|
15238
15721
|
})));
|
|
15239
|
-
addGlobalFlags(program.command("grep")).description("Search files,
|
|
15722
|
+
addGlobalFlags(program.command("grep")).description("Search files, node content, Base records, and custom prompts with one pattern (unified grep; use `assets grep` for files-only)").requiredOption("--pattern <regex>", "literal or regex pattern").option("--flags <flags>", "RegExp flags, e.g. \"i\" for case-insensitive").addOption(new Option("--sources <sources...>", "sources to scan: \"files\", \"nodes\" (Doc/HTML/whiteboard/workflow content), \"records\", and/or \"prompts\" (custom agent prompts, every locale) (default: all four)").choices([
|
|
15723
|
+
"files",
|
|
15724
|
+
"nodes",
|
|
15725
|
+
"records",
|
|
15726
|
+
"prompts"
|
|
15727
|
+
])).option("--asset-ids <ids...>", "files scope: specific asset ids").option("--drive-path <path>", "files scope: Drive/Skill mounted path prefix").option("--mime-types <types...>", "files scope: MIME types").option("--node-ids <ids...>", "nodes scope: specific node ids").option("--node-types <types...>", "nodes scope: narrow to \"doc\", \"html\", \"whiteboard\" and/or \"workflow\"").option("--base-ids <ids...>", "records scope: specific Base ids").option("--base-slugs <slugs...>", "records scope: specific Base slugs").option("--max-matches <n>", "max matches, shared across sources (default 100, cap 1000)", parsePositiveInt).option("--context-lines <n>", "lines of before/after context (default 0, cap 10)", parseNum).addHelpText("after", `
|
|
15728
|
+
Examples:
|
|
15729
|
+
busabase-cli grep --pattern "Weekly Report" --output json
|
|
15730
|
+
busabase-cli grep --pattern "refund" --flags i --sources files --drive-path "policies/"
|
|
15731
|
+
busabase-cli grep --pattern "ACME" --sources records --base-slugs contracts
|
|
15732
|
+
|
|
15733
|
+
Each file match carries \`owner\`: the skill or drive the file belongs to, with its
|
|
15734
|
+
folder path. Every requested source gets a fair share of --max-matches, so one
|
|
15735
|
+
noisy source cannot hide the rest. When the result says truncated, narrow the
|
|
15736
|
+
scope instead of raising --max-matches: pick --sources, then --base-slugs for
|
|
15737
|
+
records or --drive-path for files.`).action(runAction(state, (client, opts) => {
|
|
15240
15738
|
const filesScope = opts.assetIds || opts.drivePath || opts.mimeTypes ? {
|
|
15241
15739
|
assetIds: opts.assetIds,
|
|
15242
15740
|
drivePath: opts.drivePath,
|
|
@@ -15263,6 +15761,38 @@ Examples:
|
|
|
15263
15761
|
contextLines: opts.contextLines
|
|
15264
15762
|
});
|
|
15265
15763
|
}));
|
|
15764
|
+
const playbooks = program.command("playbooks").description("Find and open the skills and custom prompts this space defines for a job");
|
|
15765
|
+
addGlobalFlags(playbooks.command("search")).description("Call first on every user instruction: find the skills and custom node prompts (playbooks) this space already defines for the job").option("--query <phrasing>", "one phrasing of the user's intent; repeat 2–5 times, in the user's language AND English (none = browse everything, nearest first)", (value, previous) => [...previous, value], []).addOption(new Option("--kinds <kinds...>", "restrict to \"skill\" and/or \"prompt\" (default: both)").choices(["skill", "prompt"])).option("--near-node-id <id>", "the node the user is on; playbooks nearest to it rank first").option("--in-node-id <id>", "only playbooks on this node or inside its subtree").option("--limit <n>", "max items, 1–50 (default 10; browse default 30)", parsePositiveInt).option("--locale <locale>", "locale for prompt labels, e.g. \"zh-CN\" (default en)").addHelpText("after", `
|
|
15766
|
+
Examples:
|
|
15767
|
+
busabase-cli playbooks search --query "记录客户拜访" --query "log customer visit" --query "visit" --output json
|
|
15768
|
+
busabase-cli playbooks search --query "weekly report" --near-node-id nod_123
|
|
15769
|
+
|
|
15770
|
+
If an item fits, \`playbooks get\` it and follow it, then name it in your reply.
|
|
15771
|
+
If nothing fits, do the work yourself. The user's explicit words override a
|
|
15772
|
+
playbook, and a playbook never authorises approving or merging a change request.
|
|
15773
|
+
|
|
15774
|
+
Against a server older than this command, it falls back to matching skill names
|
|
15775
|
+
and descriptions only, warns on stderr, and reports coverage.prompts as
|
|
15776
|
+
"${PROMPTS_UNSUPPORTED}" — an empty result then does NOT mean there are no playbooks.`).action(runAction(state, (client, opts) => playbooksSearchCommand(client, {
|
|
15777
|
+
queries: opts.query ?? [],
|
|
15778
|
+
kinds: opts.kinds,
|
|
15779
|
+
nearNodeId: opts.nearNodeId,
|
|
15780
|
+
inNodeId: opts.inNodeId,
|
|
15781
|
+
limit: opts.limit,
|
|
15782
|
+
locale: opts.locale
|
|
15783
|
+
})));
|
|
15784
|
+
addGlobalFlags(playbooks.command("get")).description("Open one playbook: a custom prompt rendered exactly as Ask Agent sends it, or a skill's SKILL.md plus its file list").addOption(new Option("--kind <kind>", "playbook kind from `playbooks search`").choices(["prompt", "skill"]).makeOptionMandatory()).requiredOption("--node-id <id>", "nodeId from `playbooks search`").option("--key <key>", "prompt key from `playbooks search` (required for --kind prompt)").option("--locale <locale>", "locale to render the prompt in, e.g. \"zh-CN\" (default en)").addHelpText("after", `
|
|
15785
|
+
Examples:
|
|
15786
|
+
busabase-cli playbooks get --kind prompt --node-id nod_456 --key log-visit
|
|
15787
|
+
busabase-cli playbooks get --kind skill --node-id nod_789`).action(runAction(state, (client, opts) => {
|
|
15788
|
+
if (opts.kind === "prompt" && !opts.key) throw new CliOutcomeError("VALIDATION", "--key is required with --kind prompt");
|
|
15789
|
+
return client.playbooks.get({
|
|
15790
|
+
kind: opts.kind,
|
|
15791
|
+
nodeId: opts.nodeId,
|
|
15792
|
+
...opts.key ? { key: opts.key } : {},
|
|
15793
|
+
...opts.locale ? { locale: opts.locale } : {}
|
|
15794
|
+
});
|
|
15795
|
+
}));
|
|
15266
15796
|
const addUploadCommand = (parent, hidden = false) => addGlobalFlags(parent.command("upload", { noHelp: hidden })).description("Upload a file and print an asset ref for record fields").requiredOption("--file <path>", "local file to upload").option("--file-name <name>", "stored file name (default: basename of --file)").option("--mime-type <mime>", "MIME type (default: inferred from extension)").option("--context <value>", "upload context (default record-field)").addHelpText("after", `
|
|
15267
15797
|
Example:
|
|
15268
15798
|
busabase-cli assets upload --file ./cover.png --output json
|
|
@@ -15543,7 +16073,7 @@ async function runCli(argv) {
|
|
|
15543
16073
|
}
|
|
15544
16074
|
const classified = classifyError(error);
|
|
15545
16075
|
if (classified.code === "UNAUTHORIZED" || classified.code === "FORBIDDEN") {
|
|
15546
|
-
const hintConfig = state.config ?? resolveConfig({});
|
|
16076
|
+
const hintConfig = state.config ?? resolveConfig({}, { strict: false });
|
|
15547
16077
|
const profile = isMultiProfile() ? activeCredentialTarget().profile : void 0;
|
|
15548
16078
|
const procedureId = state.procedureId ?? (state.lastRequest ? matchRoute(state.lastRequest.method, state.lastRequest.pathname)?.id : void 0);
|
|
15549
16079
|
classified.hint = authRecoveryHint(classified.code, hintConfig.baseUrl, profile, {
|
|
@@ -15552,7 +16082,7 @@ async function runCli(argv) {
|
|
|
15552
16082
|
});
|
|
15553
16083
|
}
|
|
15554
16084
|
if (json) console.log(JSON.stringify(errorEnvelope(classified), null, 2));
|
|
15555
|
-
console.error(explainError(error, state.config ?? resolveConfig({})));
|
|
16085
|
+
console.error(explainError(error, state.config ?? resolveConfig({}, { strict: false })));
|
|
15556
16086
|
return classified.exitCode;
|
|
15557
16087
|
}
|
|
15558
16088
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "busa-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.90.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.90.0",
|
|
37
37
|
"commander": "^14.0.0",
|
|
38
38
|
"qrcode": "^1.5.4",
|
|
39
39
|
"tar-stream": "^3.1.7",
|