busa-cli 0.10.3 → 0.11.2
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/bin/busabase-cli.mjs +4 -7
- package/dist/{chunk-MLTXEKPL.js → chunk-S3KRMKDU.js} +244 -44
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/bin/busabase-cli.mjs
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { dirname, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
3
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const builtCli = resolve(pkgRoot, "dist/cli.js");
|
|
4
|
+
const builtCli = new URL("../dist/cli.js", import.meta.url);
|
|
5
|
+
const programmaticEntry = new URL("../dist/index.js", import.meta.url);
|
|
9
6
|
const delegatedArgvJson = process.env.BUSABASE_CLI_DELEGATED_ARGV;
|
|
10
7
|
|
|
11
8
|
if (!existsSync(builtCli)) {
|
|
@@ -22,14 +19,14 @@ if (!existsSync(builtCli)) {
|
|
|
22
19
|
try {
|
|
23
20
|
if (delegatedArgvJson) {
|
|
24
21
|
delete process.env.BUSABASE_CLI_DELEGATED_ARGV;
|
|
25
|
-
const { runCli } = await import(
|
|
22
|
+
const { runCli } = await import(programmaticEntry.href);
|
|
26
23
|
const delegatedArgv = JSON.parse(delegatedArgvJson);
|
|
27
24
|
if (!Array.isArray(delegatedArgv) || delegatedArgv.some((arg) => typeof arg !== "string")) {
|
|
28
25
|
throw new Error("BUSABASE_CLI_DELEGATED_ARGV must be a JSON string array.");
|
|
29
26
|
}
|
|
30
27
|
process.exit(await runCli(delegatedArgv));
|
|
31
28
|
}
|
|
32
|
-
await import(builtCli);
|
|
29
|
+
await import(builtCli.href);
|
|
33
30
|
} catch (error) {
|
|
34
31
|
const message = error instanceof Error ? error.message : String(error);
|
|
35
32
|
if (message.includes("busabase-sdk") && message.includes("dist/index.js")) {
|
|
@@ -284,6 +284,10 @@ var baseFieldChangeTask = {
|
|
|
284
284
|
|
|
285
285
|
// ../../packages/busabase-contract/src/tasks/change-request.ts
|
|
286
286
|
var REVIEW_GUIDANCE = "This records a HUMAN decision. Never call it unless the user explicitly asked for this specific verdict on these specific change requests. Summarising a change request for the user is not permission to approve it.";
|
|
287
|
+
var singleItemError = (result) => Object.assign(new Error(result?.error ?? "Change request action returned no result"), {
|
|
288
|
+
...result?.code ? { code: result.code } : {},
|
|
289
|
+
...result?.data === void 0 ? {} : { data: result.data }
|
|
290
|
+
});
|
|
287
291
|
var changeRequestQueryTask = {
|
|
288
292
|
name: "change_request_query",
|
|
289
293
|
cliPath: ["change-requests", "query"],
|
|
@@ -363,18 +367,15 @@ var changeRequestReviewTask = {
|
|
|
363
367
|
throw new Error("changeRequestIds must contain at least one id.");
|
|
364
368
|
}
|
|
365
369
|
const verdict = input.verdict;
|
|
366
|
-
|
|
367
|
-
return client.changeRequests.review({
|
|
368
|
-
changeRequestId: ids[0],
|
|
369
|
-
verdict,
|
|
370
|
-
...input.reason ? { reason: input.reason } : {}
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
return client.changeRequests.reviewMany({
|
|
370
|
+
const response = await client.changeRequests.review({
|
|
374
371
|
changeRequestIds: ids,
|
|
375
372
|
verdict,
|
|
376
373
|
...input.reason ? { reason: input.reason } : {}
|
|
377
374
|
});
|
|
375
|
+
if (ids.length !== 1) return response;
|
|
376
|
+
const result = response.results[0];
|
|
377
|
+
if (!result?.ok) throw singleItemError(result);
|
|
378
|
+
return result.changeRequest;
|
|
378
379
|
}
|
|
379
380
|
};
|
|
380
381
|
var changeRequestMergeTask = {
|
|
@@ -401,8 +402,15 @@ var changeRequestMergeTask = {
|
|
|
401
402
|
if (!Array.isArray(ids) || ids.length === 0) {
|
|
402
403
|
throw new Error("changeRequestIds must contain at least one id.");
|
|
403
404
|
}
|
|
404
|
-
|
|
405
|
-
|
|
405
|
+
const response = await client.changeRequests.merge({ changeRequestIds: ids });
|
|
406
|
+
if (ids.length !== 1) return response;
|
|
407
|
+
const result = response.results[0];
|
|
408
|
+
if (!result?.ok) throw singleItemError(result);
|
|
409
|
+
return {
|
|
410
|
+
changeRequest: result.changeRequest,
|
|
411
|
+
record: result.record,
|
|
412
|
+
view: result.view
|
|
413
|
+
};
|
|
406
414
|
}
|
|
407
415
|
};
|
|
408
416
|
|
|
@@ -434,25 +442,25 @@ var nodeListTask = {
|
|
|
434
442
|
{
|
|
435
443
|
path: ["skills", "list"],
|
|
436
444
|
preset: { kind: "skill" },
|
|
437
|
-
summary: "List Skill nodes
|
|
445
|
+
summary: "List Skill nodes (summaries; use `skills get` for one node's files)"
|
|
438
446
|
},
|
|
439
447
|
{
|
|
440
448
|
path: ["drives", "list"],
|
|
441
449
|
preset: { kind: "drive" },
|
|
442
|
-
summary: "List Drive nodes
|
|
450
|
+
summary: "List Drive nodes (summaries; use `drives get` for one node's files)"
|
|
443
451
|
},
|
|
444
452
|
{
|
|
445
453
|
path: ["airapps", "list"],
|
|
446
454
|
preset: { kind: "airapp" },
|
|
447
|
-
summary: "List AirApp nodes
|
|
455
|
+
summary: "List AirApp nodes (summaries; use `airapps get` for one node's files)"
|
|
448
456
|
}
|
|
449
457
|
],
|
|
450
|
-
summary: "List Skill, Drive, or AirApp nodes
|
|
451
|
-
guidance: "Returns every node of the given kind
|
|
458
|
+
summary: "List Skill, Drive, or AirApp nodes",
|
|
459
|
+
guidance: "Returns a lightweight summary row for every node of the given kind \u2014 id, name, slug, type, and metadata, but NOT the node's file list. Follow up with `node_get_file_tree` for one node's files, or `node_files_list` for just the file inventory. For the workspace tree across all node types, use `nodes_list` instead.",
|
|
452
460
|
annotations: { readOnly: true, destructive: false },
|
|
453
461
|
params: [kindParam],
|
|
454
462
|
examples: ["busabase-cli nodes list-file-trees --kind skill"],
|
|
455
|
-
execute: async (client, input) => client.
|
|
463
|
+
execute: async (client, input) => client.nodes.list({ types: [resolveKind(input.kind)] })
|
|
456
464
|
};
|
|
457
465
|
var nodeGetTask = {
|
|
458
466
|
name: "node_get_file_tree",
|
|
@@ -478,7 +486,10 @@ var nodeGetTask = {
|
|
|
478
486
|
annotations: { readOnly: true, destructive: false },
|
|
479
487
|
params: [kindParam, nodeIdParam],
|
|
480
488
|
examples: ["busabase-cli nodes get-file-tree --kind airapp --node-id nod_123"],
|
|
481
|
-
|
|
489
|
+
// Reads through the unified Node detail route. The payload is unchanged apart
|
|
490
|
+
// from the `type` discriminator the union carries, so a caller still gets
|
|
491
|
+
// `{ node, entryFile, visibility, version, files }`.
|
|
492
|
+
execute: async (client, input) => client.nodes.get({ nodeId: input.nodeId, type: resolveKind(input.kind) })
|
|
482
493
|
};
|
|
483
494
|
var nodeListFilesTask = {
|
|
484
495
|
name: "node_files_list",
|
|
@@ -567,7 +578,7 @@ var nodeFilesChangeRequestTask = {
|
|
|
567
578
|
}
|
|
568
579
|
],
|
|
569
580
|
summary: "Propose file changes inside a Skill, Drive, or AirApp node",
|
|
570
|
-
guidance:
|
|
581
|
+
guidance: 'Approval-first: this proposes a ChangeRequest for a human to review, it does not write directly. Each operation is one of create / update / delete / metadata_update. Include `baseContentHash` (from `node_file_read`) on an update so a concurrent edit is caught. For kind "airapp", keep the project runnable by `npm run dev` \u2014 editing package.json must leave a "dev" script starting a plain Node server, not a bundler dev server.',
|
|
571
582
|
annotations: { readOnly: false, destructive: false },
|
|
572
583
|
params: [
|
|
573
584
|
kindParam,
|
|
@@ -1189,7 +1200,7 @@ var nodeCreateTask = {
|
|
|
1189
1200
|
name: "files",
|
|
1190
1201
|
appliesWhen: { param: "type", values: ["skill", "drive", "airapp"] },
|
|
1191
1202
|
kind: "json",
|
|
1192
|
-
description: 'Seed files for a Skill/Drive/AirApp: [{"path":"SKILL.md","content":"..."}]. Layered over the default scaffold unless mergeMode is "replace".'
|
|
1203
|
+
description: 'Seed files for a Skill/Drive/AirApp: [{"path":"SKILL.md","content":"..."}]. Layered over the default scaffold unless mergeMode is "replace" \u2014 a path you supply REPLACES the scaffold\'s file at that path. For --type airapp the project MUST be runnable by `npm run dev`: give package.json a "dev" script running a plain Node server (`node server.js`, Hono or node:http). A bundler dev server (Vite/webpack/Next) and any native-binary dependency CANNOT boot in the AirApp runtime, and browser files must need no build step.'
|
|
1193
1204
|
},
|
|
1194
1205
|
{
|
|
1195
1206
|
name: "mergeMode",
|
|
@@ -1285,11 +1296,16 @@ var nodeCreateTask = {
|
|
|
1285
1296
|
|
|
1286
1297
|
// ../../packages/busabase-contract/src/tasks/record-change.ts
|
|
1287
1298
|
var OPERATIONS2 = ["update", "delete", "restore"];
|
|
1299
|
+
var mergeIntent2 = (input) => {
|
|
1300
|
+
if (input.requireReview) return { autoMerge: false };
|
|
1301
|
+
if (input.autoMerge) return { autoMerge: true };
|
|
1302
|
+
return {};
|
|
1303
|
+
};
|
|
1288
1304
|
var recordChangeTask = {
|
|
1289
1305
|
name: "record_change_request",
|
|
1290
1306
|
cliPath: ["records", "change-request"],
|
|
1291
1307
|
summary: "Propose a change to an existing record (update / delete / restore)",
|
|
1292
|
-
guidance: "
|
|
1308
|
+
guidance: "Review is permission-aware, decided server-side: `update` merges immediately when your key has write access on the Base's node and lands as a pending ChangeRequest otherwise \u2014 check the response's `materialized` field to see which happened. Pass explicit `autoMerge: false` to force review even when you could write directly. `delete` and `restore` are always review-first regardless of permission, by design. `delete` ARCHIVES the record \u2014 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.",
|
|
1293
1309
|
annotations: { readOnly: false, destructive: false },
|
|
1294
1310
|
params: [
|
|
1295
1311
|
{ name: "recordId", kind: "string", required: true, description: "Record to change." },
|
|
@@ -1311,10 +1327,23 @@ var recordChangeTask = {
|
|
|
1311
1327
|
kind: "string",
|
|
1312
1328
|
description: 'Explanation for the reviewer \u2014 imperative verb + what + why, e.g. "Update deal stage to qualified \u2014 demo booked".'
|
|
1313
1329
|
},
|
|
1314
|
-
{ name: "submittedBy", kind: "string", description: "Producer label recorded on the change." }
|
|
1330
|
+
{ name: "submittedBy", kind: "string", description: "Producer label recorded on the change." },
|
|
1331
|
+
{
|
|
1332
|
+
name: "autoMerge",
|
|
1333
|
+
kind: "boolean",
|
|
1334
|
+
appliesWhen: { param: "operation", values: ["update"] },
|
|
1335
|
+
description: "Skip review and apply the update immediately if you have write access. Not a permission override \u2014 a changeRequest-level key still gets a pending CR. Default is permission-aware: merge when you can, otherwise propose."
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
name: "requireReview",
|
|
1339
|
+
kind: "boolean",
|
|
1340
|
+
appliesWhen: { param: "operation", values: ["update"] },
|
|
1341
|
+
description: "Always propose a pending ChangeRequest for this update, even with write access. delete/restore are review-first regardless."
|
|
1342
|
+
}
|
|
1315
1343
|
],
|
|
1316
1344
|
examples: [
|
|
1317
1345
|
`busabase-cli records change-request --record-id rec_1 --operation update --fields-json '{"status":"published"}'`,
|
|
1346
|
+
`busabase-cli records change-request --record-id rec_1 --operation update --fields-json '{"status":"published"}' --require-review`,
|
|
1318
1347
|
"busabase-cli records change-request --record-id rec_1 --operation delete"
|
|
1319
1348
|
],
|
|
1320
1349
|
execute: async (client, input) => {
|
|
@@ -1330,7 +1359,10 @@ var recordChangeTask = {
|
|
|
1330
1359
|
return client.records.changeRequest({
|
|
1331
1360
|
...common,
|
|
1332
1361
|
operation: "update",
|
|
1333
|
-
fields: input.fields
|
|
1362
|
+
fields: input.fields,
|
|
1363
|
+
// Only `update` carries the merge intent — the endpoint's delete/restore
|
|
1364
|
+
// branches have no `autoMerge` in their schema at all, by design.
|
|
1365
|
+
...mergeIntent2(input)
|
|
1334
1366
|
});
|
|
1335
1367
|
}
|
|
1336
1368
|
if (input.operation === "delete") {
|
|
@@ -1433,11 +1465,16 @@ var recordQueryTask = {
|
|
|
1433
1465
|
|
|
1434
1466
|
// ../../packages/busabase-contract/src/tasks/view-change.ts
|
|
1435
1467
|
var ACTIONS = ["create", "update", "delete", "restore"];
|
|
1468
|
+
var mergeIntent3 = (input) => {
|
|
1469
|
+
if (input.requireReview) return { autoMerge: false };
|
|
1470
|
+
if (input.autoMerge) return { autoMerge: true };
|
|
1471
|
+
return {};
|
|
1472
|
+
};
|
|
1436
1473
|
var viewChangeTask = {
|
|
1437
1474
|
name: "view_change_request",
|
|
1438
1475
|
cliPath: ["views", "change-request"],
|
|
1439
1476
|
summary: "Propose a Base view change (create / update / delete / restore)",
|
|
1440
|
-
guidance: "
|
|
1477
|
+
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 \u2014 check the response's `materialized` field to see which happened. Pass requireReview to always propose instead of merging. Views are structure, not content \u2014 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 \u2014 the endpoint split follows whichever id identifies the target, which this task handles for you.",
|
|
1441
1478
|
annotations: { readOnly: false, destructive: false },
|
|
1442
1479
|
params: [
|
|
1443
1480
|
{
|
|
@@ -1484,16 +1521,30 @@ var viewChangeTask = {
|
|
|
1484
1521
|
description: "Changes to apply to the view (update only)."
|
|
1485
1522
|
},
|
|
1486
1523
|
{ name: "message", kind: "string", description: "Explanation for the reviewer." },
|
|
1487
|
-
{ name: "submittedBy", kind: "string", description: "Producer label recorded on the change." }
|
|
1524
|
+
{ name: "submittedBy", kind: "string", description: "Producer label recorded on the change." },
|
|
1525
|
+
{
|
|
1526
|
+
name: "autoMerge",
|
|
1527
|
+
kind: "boolean",
|
|
1528
|
+
description: "Skip review and apply the change immediately if you have write access. Not a permission override \u2014 a changeRequest-level key still gets a pending CR. Default is permission-aware: merge when you can, otherwise propose."
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
name: "requireReview",
|
|
1532
|
+
kind: "boolean",
|
|
1533
|
+
description: "Always propose a pending ChangeRequest, even with write access."
|
|
1534
|
+
}
|
|
1488
1535
|
],
|
|
1489
1536
|
examples: [
|
|
1490
1537
|
'busabase-cli views change-request --action create --base-id bas_1 --name "Published" --type table',
|
|
1538
|
+
'busabase-cli views change-request --action create --base-id bas_1 --name "Published" --require-review',
|
|
1491
1539
|
"busabase-cli views change-request --action delete --view-id viw_1"
|
|
1492
1540
|
],
|
|
1493
1541
|
execute: async (client, input) => {
|
|
1494
1542
|
const meta = {
|
|
1495
1543
|
...input.message ? { message: input.message } : {},
|
|
1496
|
-
...input.submittedBy ? { submittedBy: input.submittedBy } : {}
|
|
1544
|
+
...input.submittedBy ? { submittedBy: input.submittedBy } : {},
|
|
1545
|
+
// Tri-state, like node_create's: omitted keeps the endpoint's
|
|
1546
|
+
// permission-aware default, explicit true/false forces the branch.
|
|
1547
|
+
...mergeIntent3(input)
|
|
1497
1548
|
};
|
|
1498
1549
|
if (input.action === "create") {
|
|
1499
1550
|
if (!input.baseId || !input.name) {
|
|
@@ -2243,8 +2294,11 @@ async function exportFull(options) {
|
|
|
2243
2294
|
let docBodyCount = 0;
|
|
2244
2295
|
for (const node of docNodes) {
|
|
2245
2296
|
const nodeId = node.id;
|
|
2246
|
-
const
|
|
2247
|
-
|
|
2297
|
+
const detail = await client.nodes.get({ nodeId, type: "doc" });
|
|
2298
|
+
if (detail.type !== "doc") {
|
|
2299
|
+
throw new Error(`Expected a doc node for ${nodeId}, got "${detail.type}"`);
|
|
2300
|
+
}
|
|
2301
|
+
await writer.addBuffer(`docs/${nodeId}.md`, detail.body ?? "");
|
|
2248
2302
|
docBodyCount += 1;
|
|
2249
2303
|
}
|
|
2250
2304
|
log(`exported ${docBodyCount} doc bodies`);
|
|
@@ -2659,9 +2713,15 @@ async function ask2(question) {
|
|
|
2659
2713
|
function openBrowser(url) {
|
|
2660
2714
|
const platform = process.platform;
|
|
2661
2715
|
const [command, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
2716
|
+
const reportFailure = () => {
|
|
2717
|
+
say2("Could not open a browser automatically. Open the URL above manually.");
|
|
2718
|
+
};
|
|
2662
2719
|
try {
|
|
2663
|
-
spawn(command, args, { stdio: "ignore", detached: true })
|
|
2720
|
+
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
2721
|
+
child.once("error", reportFailure);
|
|
2722
|
+
child.unref();
|
|
2664
2723
|
} catch {
|
|
2724
|
+
reportFailure();
|
|
2665
2725
|
}
|
|
2666
2726
|
}
|
|
2667
2727
|
async function verifyAuth(baseUrl, token) {
|
|
@@ -3408,6 +3468,10 @@ var fieldChangeRequestInputSchema = z6.discriminatedUnion("operation", [
|
|
|
3408
3468
|
reorderFieldsChangeRequestInputSchema.extend({ operation: z6.literal("reorder"), ...withBaseId }),
|
|
3409
3469
|
restoreFieldChangeRequestInputSchema.extend({ operation: z6.literal("restore"), ...withBaseId })
|
|
3410
3470
|
]);
|
|
3471
|
+
var baseLifecycleChangeRequestInputSchema = z6.discriminatedUnion("operation", [
|
|
3472
|
+
archiveBaseInputSchema.extend({ operation: z6.literal("archive"), ...withBaseId }),
|
|
3473
|
+
restoreBaseInputSchema.extend({ operation: z6.literal("restore"), ...withBaseId })
|
|
3474
|
+
]);
|
|
3411
3475
|
|
|
3412
3476
|
// ../../packages/busabase-contract/src/domains/base/contract/view-schemas.ts
|
|
3413
3477
|
import { z as z8 } from "zod";
|
|
@@ -3465,7 +3529,33 @@ var listNodesInputSchema = z7.object({
|
|
|
3465
3529
|
* soft-archived nodes for the Trash view — no `parentId`/`depth` walk, since
|
|
3466
3530
|
* archived nodes are shown as a list, not a tree.
|
|
3467
3531
|
*/
|
|
3468
|
-
status: z7.enum(["active", "archived"]).optional().default("active")
|
|
3532
|
+
status: z7.enum(["active", "archived"]).optional().default("active"),
|
|
3533
|
+
/**
|
|
3534
|
+
* Narrow to specific node types and return a FLAT list of lightweight node
|
|
3535
|
+
* summaries (`children: []`) instead of walking the tree. This is what
|
|
3536
|
+
* replaced the four retired narrow listings (`GET /docs`, `/files`,
|
|
3537
|
+
* `/folders`, `/file-trees`); file-trees are selected with their real
|
|
3538
|
+
* discriminators `skill` / `drive` / `airapp`, since there is no synthetic
|
|
3539
|
+
* "file-tree" node type.
|
|
3540
|
+
*
|
|
3541
|
+
* Omitting `types` leaves every existing caller on exactly today's
|
|
3542
|
+
* behaviour (full tree, or a `parentId`/`depth`-bounded walk, or the
|
|
3543
|
+
* archived flat list) — the two modes never interfere.
|
|
3544
|
+
*
|
|
3545
|
+
* NOTE — no `projection` parameter, deliberately. The consolidation roadmap
|
|
3546
|
+
* sketched `?projection=summary`, but it also rules out adding
|
|
3547
|
+
* `projection=detail` in this batch (the retired detail lists were the
|
|
3548
|
+
* N+1 payloads this change exists to remove). That would leave a parameter
|
|
3549
|
+
* with exactly one legal value, which is noise in OpenAPI/MCP/CLI rather
|
|
3550
|
+
* than a decision a caller gets to make. Detail is `GET /nodes/{nodeId}`.
|
|
3551
|
+
*
|
|
3552
|
+
* A GET query param that occurs exactly once (`?types=doc`) arrives as a
|
|
3553
|
+
* bare string, not a 1-element array — only a REPEATED occurrence
|
|
3554
|
+
* (`?types=doc&types=file`) becomes an array. Accept both and normalize.
|
|
3555
|
+
*/
|
|
3556
|
+
types: z7.union([z7.array(z7.enum(NODE_TYPES)), z7.enum(NODE_TYPES)]).transform((value) => Array.isArray(value) ? value : [value]).optional().describe(
|
|
3557
|
+
"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}."
|
|
3558
|
+
)
|
|
3469
3559
|
}).optional();
|
|
3470
3560
|
var isDescendantInputSchema = z7.object({
|
|
3471
3561
|
nodeId: z7.string(),
|
|
@@ -3633,11 +3723,19 @@ var liveEventSchema = z7.object({
|
|
|
3633
3723
|
// via the audit funnel, but nothing needs reviewing). Consumed by
|
|
3634
3724
|
// `use-live-sync.ts` to pop a desktop Notification, and by
|
|
3635
3725
|
// busabase-cloud's host hook to persist an inbox notification row.
|
|
3636
|
-
"change_request.pending_review"
|
|
3726
|
+
"change_request.pending_review",
|
|
3727
|
+
// A node's metadata was written directly, outside the change-request flow
|
|
3728
|
+
// (`PATCH /api/v1/nodes/{nodeId}/metadata` — agents, the SDK, an MCP tool,
|
|
3729
|
+
// and every rich-node editor's own Save). Carries the touched node in
|
|
3730
|
+
// `nodeIds` so open dashboards refetch the node tree instead of showing a
|
|
3731
|
+
// stale whiteboard/workflow/HTML document until the next reload.
|
|
3732
|
+
"node.metadata_updated"
|
|
3637
3733
|
]),
|
|
3638
3734
|
spaceId: z7.string(),
|
|
3639
3735
|
actorId: z7.string(),
|
|
3640
|
-
|
|
3736
|
+
// Null for events that aren't about a change request at all
|
|
3737
|
+
// (`node.metadata_updated`), which is every direct, auto-audited write.
|
|
3738
|
+
changeRequestId: z7.string().nullable(),
|
|
3641
3739
|
baseId: z7.string().nullable(),
|
|
3642
3740
|
nodeIds: z7.array(z7.string()),
|
|
3643
3741
|
recordIds: z7.array(z7.string()),
|
|
@@ -3808,6 +3906,19 @@ var listChangeRequestsResponseSchema = z7.object({
|
|
|
3808
3906
|
changeRequests: z7.array(changeRequestSchema),
|
|
3809
3907
|
nextCursor: z7.string().nullable()
|
|
3810
3908
|
});
|
|
3909
|
+
var listChangeRequestsPageInputSchema = z7.object({
|
|
3910
|
+
page: z7.coerce.number().int().min(1).optional().default(1),
|
|
3911
|
+
pageSize: z7.coerce.number().int().min(1).max(100).optional().default(50),
|
|
3912
|
+
status: z7.array(changeRequestStatusSchema).optional(),
|
|
3913
|
+
mine: z7.boolean().optional()
|
|
3914
|
+
}).optional().default({ page: 1, pageSize: 50 });
|
|
3915
|
+
var listChangeRequestsPageResponseSchema = z7.object({
|
|
3916
|
+
changeRequests: z7.array(changeRequestSchema),
|
|
3917
|
+
total: z7.number().int().nonnegative(),
|
|
3918
|
+
totalPages: z7.number().int().nonnegative(),
|
|
3919
|
+
page: z7.number().int().min(1),
|
|
3920
|
+
pageSize: z7.number().int().min(1).max(100)
|
|
3921
|
+
});
|
|
3811
3922
|
var changeRequestCountsSchema = z7.object({
|
|
3812
3923
|
review: z7.number().int().nonnegative(),
|
|
3813
3924
|
changes: z7.number().int().nonnegative(),
|
|
@@ -3948,6 +4059,9 @@ var viewSchema = z8.object({
|
|
|
3948
4059
|
createdAt: z8.string(),
|
|
3949
4060
|
updatedAt: z8.string()
|
|
3950
4061
|
});
|
|
4062
|
+
var autoMergeSchema = z8.boolean().optional().describe(
|
|
4063
|
+
"Whether to approve and merge this view change immediately. Omitted defaults to merging immediately if the actor has write access on the Base's node, otherwise falling back to a pending Change Request; pass explicit false to force review even with write access."
|
|
4064
|
+
);
|
|
3951
4065
|
var createViewInputSchema = z8.object({
|
|
3952
4066
|
config: viewConfigSchema.optional().default({ filters: [], sorts: [] }),
|
|
3953
4067
|
description: z8.string().optional().default(""),
|
|
@@ -3955,7 +4069,8 @@ var createViewInputSchema = z8.object({
|
|
|
3955
4069
|
name: z8.string().min(1),
|
|
3956
4070
|
type: viewTypeSchema.optional().default("table"),
|
|
3957
4071
|
slug: z8.string().min(1).regex(/^[a-z0-9-]+$/).optional(),
|
|
3958
|
-
submittedBy: z8.string().optional().default("local-producer")
|
|
4072
|
+
submittedBy: z8.string().optional().default("local-producer"),
|
|
4073
|
+
autoMerge: autoMergeSchema
|
|
3959
4074
|
});
|
|
3960
4075
|
var updateViewInputSchema = z8.object({
|
|
3961
4076
|
config: viewConfigSchema.optional(),
|
|
@@ -3963,15 +4078,18 @@ var updateViewInputSchema = z8.object({
|
|
|
3963
4078
|
message: z8.string().optional().default("Update view"),
|
|
3964
4079
|
name: z8.string().min(1).optional(),
|
|
3965
4080
|
type: viewTypeSchema.optional(),
|
|
3966
|
-
submittedBy: z8.string().optional().default("local-producer")
|
|
4081
|
+
submittedBy: z8.string().optional().default("local-producer"),
|
|
4082
|
+
autoMerge: autoMergeSchema
|
|
3967
4083
|
});
|
|
3968
4084
|
var deleteViewInputSchema = z8.object({
|
|
3969
4085
|
message: z8.string().optional().default("Delete view"),
|
|
3970
|
-
submittedBy: z8.string().optional().default("local-producer")
|
|
4086
|
+
submittedBy: z8.string().optional().default("local-producer"),
|
|
4087
|
+
autoMerge: autoMergeSchema
|
|
3971
4088
|
});
|
|
3972
4089
|
var restoreViewInputSchema = z8.object({
|
|
3973
4090
|
message: z8.string().optional().default("Restore view"),
|
|
3974
|
-
submittedBy: z8.string().optional().default("local-producer")
|
|
4091
|
+
submittedBy: z8.string().optional().default("local-producer"),
|
|
4092
|
+
autoMerge: autoMergeSchema
|
|
3975
4093
|
});
|
|
3976
4094
|
var viewChangeRequestInputSchema = z8.discriminatedUnion("operation", [
|
|
3977
4095
|
createViewInputSchema.extend({
|
|
@@ -4486,16 +4604,25 @@ var createBase = async (client, node, parentNodeId, options, state, bases, field
|
|
|
4486
4604
|
bases.push({ node, baseId: result.id });
|
|
4487
4605
|
indexFields(fieldIds, node.slug, result.fields);
|
|
4488
4606
|
for (const view of node.base.views) {
|
|
4489
|
-
const
|
|
4607
|
+
const created = await client.views.changeRequest({
|
|
4490
4608
|
operation: "create",
|
|
4491
4609
|
baseId: result.id,
|
|
4492
4610
|
slug: view.slug,
|
|
4493
4611
|
name: view.name,
|
|
4494
4612
|
description: view.description,
|
|
4495
4613
|
config: view.config,
|
|
4496
|
-
submittedBy: options.submittedBy
|
|
4614
|
+
submittedBy: options.submittedBy,
|
|
4615
|
+
// Structure, exactly like the Base above — so ask for it outright instead
|
|
4616
|
+
// of proposing it and immediately approving+merging our own proposal,
|
|
4617
|
+
// which is what this loop used to do only because the endpoint had no
|
|
4618
|
+
// `autoMerge`.
|
|
4619
|
+
autoMerge: true
|
|
4497
4620
|
});
|
|
4498
|
-
|
|
4621
|
+
if (!created.materialized) {
|
|
4622
|
+
throw new Error(
|
|
4623
|
+
`View "${view.slug}" was requested immediately but the server returned a pending change request; install cannot leave a Base's views behind in review.`
|
|
4624
|
+
);
|
|
4625
|
+
}
|
|
4499
4626
|
state.created.views++;
|
|
4500
4627
|
}
|
|
4501
4628
|
};
|
|
@@ -4718,8 +4845,32 @@ var toKeyArray = (value) => {
|
|
|
4718
4845
|
return [];
|
|
4719
4846
|
};
|
|
4720
4847
|
var approveAndMerge = async (client, changeRequestId) => {
|
|
4721
|
-
await client.changeRequests.review({
|
|
4722
|
-
|
|
4848
|
+
const reviewed = await client.changeRequests.review({
|
|
4849
|
+
changeRequestIds: [changeRequestId],
|
|
4850
|
+
verdict: "approved"
|
|
4851
|
+
});
|
|
4852
|
+
const reviewResult = reviewed.results[0];
|
|
4853
|
+
if (!reviewResult?.ok) {
|
|
4854
|
+
throw Object.assign(
|
|
4855
|
+
new Error(reviewResult?.error ?? "Change request review returned no result"),
|
|
4856
|
+
{
|
|
4857
|
+
code: reviewResult?.code,
|
|
4858
|
+
data: reviewResult?.data
|
|
4859
|
+
}
|
|
4860
|
+
);
|
|
4861
|
+
}
|
|
4862
|
+
const merged = await client.changeRequests.merge({ changeRequestIds: [changeRequestId] });
|
|
4863
|
+
const mergeResult = merged.results[0];
|
|
4864
|
+
if (!mergeResult?.ok) {
|
|
4865
|
+
throw Object.assign(
|
|
4866
|
+
new Error(mergeResult?.error ?? "Change request merge returned no result"),
|
|
4867
|
+
{
|
|
4868
|
+
code: mergeResult?.code,
|
|
4869
|
+
data: mergeResult?.data
|
|
4870
|
+
}
|
|
4871
|
+
);
|
|
4872
|
+
}
|
|
4873
|
+
return mergeResult;
|
|
4723
4874
|
};
|
|
4724
4875
|
|
|
4725
4876
|
// ../../packages/busabase-package/src/collect.ts
|
|
@@ -4766,7 +4917,10 @@ var collectNode = async (client, source, options) => {
|
|
|
4766
4917
|
children: await collectNodes(client, source.children ?? [], options)
|
|
4767
4918
|
};
|
|
4768
4919
|
case "doc": {
|
|
4769
|
-
const doc = await client.
|
|
4920
|
+
const doc = await client.nodes.get({ nodeId: source.id, type: "doc" });
|
|
4921
|
+
if (doc.type !== "doc") {
|
|
4922
|
+
throw new Error(`Expected a doc node for "${source.slug}", got "${doc.type}".`);
|
|
4923
|
+
}
|
|
4770
4924
|
return { ...common, type: "doc", body: doc.body };
|
|
4771
4925
|
}
|
|
4772
4926
|
case "base":
|
|
@@ -4950,7 +5104,10 @@ var collectFileTree = async (client, source, type, common, options) => {
|
|
|
4950
5104
|
};
|
|
4951
5105
|
};
|
|
4952
5106
|
var collectFile = async (client, source, common, options) => {
|
|
4953
|
-
const fileNode = await client.
|
|
5107
|
+
const fileNode = await client.nodes.get({ nodeId: source.id, type: "file" });
|
|
5108
|
+
if (fileNode.type !== "file") {
|
|
5109
|
+
throw new Error(`Expected a file node for "${source.slug}", got "${fileNode.type}".`);
|
|
5110
|
+
}
|
|
4954
5111
|
const asset = fileNode.asset;
|
|
4955
5112
|
if (!asset?.url) {
|
|
4956
5113
|
options.warn(`Skipped file node "${source.slug}": it has no downloadable asset.`);
|
|
@@ -6488,12 +6645,17 @@ var GENERATED_SKIP = /* @__PURE__ */ new Set([
|
|
|
6488
6645
|
// The `/file-trees` group is exposed by the task layer as `nodes
|
|
6489
6646
|
// list-file-trees` / `get-file-tree` / `files` / `read-file` /
|
|
6490
6647
|
// `files-change-request`, each taking `--kind` (see tasks/file-tree.ts).
|
|
6491
|
-
|
|
6648
|
+
// `fileTrees.list`/`fileTrees.get` are absent because the procedures are: the
|
|
6649
|
+
// two tasks that used to call them now call `nodes.list({ types })` /
|
|
6650
|
+
// `nodes.get`, so there is nothing left for the generator to skip.
|
|
6492
6651
|
"fileTrees.create",
|
|
6493
|
-
"fileTrees.get",
|
|
6494
6652
|
"fileTrees.listFiles",
|
|
6495
6653
|
"fileTrees.readFile",
|
|
6496
6654
|
"fileTrees.createChangeRequest"
|
|
6655
|
+
// `nodes get` is the curated `docs|files|folders get` facade's underlying
|
|
6656
|
+
// route, but it stays generated too: it is the ONE command that reads a node
|
|
6657
|
+
// of any type without knowing the type first, which is the whole point of the
|
|
6658
|
+
// consolidation.
|
|
6497
6659
|
]);
|
|
6498
6660
|
function registerGeneratedCommands(program, state) {
|
|
6499
6661
|
const getGroup = (displayName, tag) => program.commands.find((c2) => c2.name() === displayName) ?? program.command(displayName).description(tag ?? `${displayName} endpoints`);
|
|
@@ -7150,6 +7312,44 @@ To add content to a space that is already in use, use \`busabase-cli install\` i
|
|
|
7150
7312
|
);
|
|
7151
7313
|
})
|
|
7152
7314
|
);
|
|
7315
|
+
for (const facade of [
|
|
7316
|
+
{
|
|
7317
|
+
group: "docs",
|
|
7318
|
+
type: "doc",
|
|
7319
|
+
label: "Doc",
|
|
7320
|
+
detail: "body included",
|
|
7321
|
+
omitted: "no bodies"
|
|
7322
|
+
},
|
|
7323
|
+
{
|
|
7324
|
+
group: "files",
|
|
7325
|
+
type: "file",
|
|
7326
|
+
label: "File",
|
|
7327
|
+
detail: "backing Asset included",
|
|
7328
|
+
omitted: "no Asset detail"
|
|
7329
|
+
},
|
|
7330
|
+
{
|
|
7331
|
+
group: "folders",
|
|
7332
|
+
type: "folder",
|
|
7333
|
+
label: "Folder",
|
|
7334
|
+
detail: "direct children included",
|
|
7335
|
+
omitted: "no children"
|
|
7336
|
+
}
|
|
7337
|
+
]) {
|
|
7338
|
+
const group = program.commands.find((c2) => c2.name() === facade.group) ?? program.command(facade.group).description(`${facade.label} nodes`);
|
|
7339
|
+
addGlobalFlags(group.command("list")).description(`List ${facade.label} nodes \u2014 summaries only (${facade.omitted})`).addHelpText(
|
|
7340
|
+
"after",
|
|
7341
|
+
`
|
|
7342
|
+
OpenAPI: GET /nodes?types=${facade.type}
|
|
7343
|
+
Follow up with \`${facade.group} get --node-id <id>\` for one node's full detail.`
|
|
7344
|
+
).action(runAction(state, (client) => client.nodes.list({ types: [facade.type] })));
|
|
7345
|
+
addGlobalFlags(group.command("get")).description(`Get one ${facade.label} node (${facade.detail})`).requiredOption("--node-id <id>", `${facade.label} node id, or a slug unique to this type`).addHelpText("after", `
|
|
7346
|
+
OpenAPI: GET /nodes/{nodeId}?type=${facade.type}`).action(
|
|
7347
|
+
runAction(
|
|
7348
|
+
state,
|
|
7349
|
+
(client, opts) => client.nodes.get({ nodeId: opts.nodeId, type: facade.type })
|
|
7350
|
+
)
|
|
7351
|
+
);
|
|
7352
|
+
}
|
|
7153
7353
|
registerTaskCommands(program, BUSABASE_TASKS, {
|
|
7154
7354
|
addGlobalFlags,
|
|
7155
7355
|
parseFieldSpecs,
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "busa-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.2",
|
|
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.11.2",
|
|
37
37
|
"commander": "^14.0.0",
|
|
38
38
|
"tar-stream": "^3.1.7",
|
|
39
39
|
"yaml": "^2.9.0",
|