busabase-sdk 0.18.0 → 0.19.1

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/airapp.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as BusabaseClient } from "./client-CbUceGzy.js";
1
+ import { t as BusabaseClient } from "./client-DPyDyOdK.js";
2
2
  //#region src/airapp.d.ts
3
3
  type NodeChangeRequestInput = Parameters<BusabaseClient["nodes"]["createChangeRequest"]>[0];
4
4
  type NodeOperationInput = NodeChangeRequestInput["operations"][number];
@@ -87,6 +87,15 @@ interface AirAppResourceConfig {
87
87
  /**
88
88
  * The ownership stamp written into `node.metadata`.
89
89
  *
90
+ * Structurally identical to (and kept in lockstep with) the contract package's
91
+ * `AppResourceOwnership`, which `busabase-package`'s installer writes for the
92
+ * SAME resources when a user installs the app from the Template Center instead
93
+ * of running its `setup.mjs`. The two writers only recognise each other's work
94
+ * by this shape — drift means a user who installed through the UI and then ran
95
+ * the skill in their shell hits `SETUP_CONFLICT` on their own data. The
96
+ * assertion below is what makes that drift a compile error rather than a
97
+ * support ticket.
98
+ *
90
99
  * A type alias rather than an `interface` on purpose: `nodes.updateMetadata`
91
100
  * and the create operations take `Record<string, unknown>`, and an interface —
92
101
  * being open to declaration merging — is not assignable to an index signature.
package/dist/airapp.js CHANGED
@@ -1,5 +1,155 @@
1
+ import { z } from "zod";
2
+ const TemplateAirAppRefSchema = z.object({
3
+ /** Slug of the `content/<dir>` holding the AirApp. */
4
+ slug: z.string().min(1),
5
+ role: z.enum([
6
+ "primary",
7
+ "admin",
8
+ "public",
9
+ "tool"
10
+ ]),
11
+ label: z.string().optional()
12
+ });
13
+ /**
14
+ * Secrets the app expects to find in the Vault.
15
+ *
16
+ * DECLARED, never created: the package format has no slot for secret values and
17
+ * must not grow one (the same "you cannot leak what the format cannot express"
18
+ * rule the whole format is built on). Install surfaces these as a post-install
19
+ * prompt; the user fills them in the Vault themselves.
20
+ */
21
+ const TemplateSecretSchema = z.object({
22
+ key: z.string().min(1),
23
+ description: z.string().default(""),
24
+ required: z.boolean().default(true)
25
+ });
26
+ z.object({
27
+ /** Template Center category, e.g. `"crm"`, `"email"`, `"content"`. */
28
+ category: z.string().min(1),
29
+ tags: z.array(z.string()).default([]),
30
+ /** Card/detail screenshots, package-relative (`assets/screenshots/overview.webp`). */
31
+ screenshots: z.array(z.string()).default([]),
32
+ /**
33
+ * Ready-made prompts shown after install ("Ask agent" prefills the first).
34
+ *
35
+ * They are the difference between a folder of tables and something a user can
36
+ * *use*: the point of a template is that the agent already knows the job, and
37
+ * these are how that is made visible rather than left for the user to guess.
38
+ */
39
+ agentPrompts: z.array(z.string()).default([]),
40
+ /** Single-AirApp shorthand. Mutually exclusive with `airapps`. */
41
+ airapp: z.string().optional(),
42
+ /** Multi-AirApp form. Exactly one entry must have `role: "primary"`. */
43
+ airapps: z.array(TemplateAirAppRefSchema).optional(),
44
+ /**
45
+ * Bumped by the author when the declared resource shape changes.
46
+ *
47
+ * Part of the ownership stamp, so BOTH doors must agree on it: the installer
48
+ * writes it, and a skill's own `setup.mjs` compares against it to decide
49
+ * whether a node it finds is its own current shape or an older one to repair.
50
+ * Defaulted rather than required so an author who never versions their app
51
+ * still gets a stamp both sides recognise.
52
+ */
53
+ schemaVersion: z.number().int().nonnegative().default(1),
54
+ vaultNamespace: z.string().optional(),
55
+ secrets: z.array(TemplateSecretSchema).default([]),
56
+ requires: z.object({ airapp: z.boolean().optional() }).default({})
57
+ });
58
+ /**
59
+ * `metadata.busabase` inside the root `SKILL.md`'s YAML frontmatter.
60
+ *
61
+ * `template: true` is an EXPLICIT opt-in, not an inference from "this skill
62
+ * happens to contain a package". Publishing a template means accepting that
63
+ * installers will run its AirApp code and feed its SKILL.md to their agent; that
64
+ * deserves a deliberate flag rather than a side effect of directory shape.
65
+ */
66
+ const SkillBusabaseMetadataSchema = z.object({
67
+ template: z.boolean().default(false),
68
+ folderSlug: z.string().optional(),
69
+ /** Resource keys the manual talks about; each must exist under `content/`. */
70
+ resources: z.array(z.string()).default([]),
71
+ risk: z.string().optional()
72
+ });
73
+ z.object({
74
+ name: z.string().min(1),
75
+ description: z.string().default(""),
76
+ metadata: z.object({ busabase: SkillBusabaseMetadataSchema.optional() }).passthrough().optional()
77
+ });
78
+ /** Stamp on every resource node (Base, Drive, AirApp, …) an app owns. */
79
+ const AppResourceOwnershipSchema = z.object({
80
+ appId: z.string().min(1),
81
+ /** Stable internal handle (`"contacts"`), NOT the installed slug. */
82
+ resourceKey: z.string().min(1),
83
+ schemaVersion: z.number().int().nonnegative()
84
+ });
85
+ /**
86
+ * The `resourceKey` reserved for an app's root Folder.
87
+ *
88
+ * `busabase-sdk` recognises an app's own Folder by looking for exactly this
89
+ * value (`ownsAppRoot`), so the installer must write it too — a Folder stamped
90
+ * with anything else reads as a stranger's, and the skill's own `setup.mjs`
91
+ * then refuses to touch its own workspace with `SETUP_CONFLICT`. Exported so
92
+ * neither side carries the string literal privately.
93
+ */
94
+ const APP_ROOT_RESOURCE_KEY = "app-root";
95
+ AppResourceOwnershipSchema.extend({
96
+ resourceKey: z.literal(APP_ROOT_RESOURCE_KEY),
97
+ version: z.string().optional(),
98
+ source: z.object({
99
+ repo: z.string().optional(),
100
+ ref: z.string().optional(),
101
+ subdir: z.string().optional()
102
+ }).optional(),
103
+ installedAt: z.string().optional()
104
+ });
105
+ z.object({
106
+ appId: z.string().min(1),
107
+ ["isTemplateSkill"]: z.literal(true)
108
+ });
109
+ //#endregion
1
110
  //#region src/airapp.ts
2
111
  /**
112
+ * AirApp resource provisioning — how an app claims (or creates) the Folder and
113
+ * Bases it declares, exactly once, without ever taking over someone else's.
114
+ *
115
+ * Every App-in-Skill shipped a byte-identical copy of this module (280 lines ×
116
+ * 65 apps, two spellings). That is the wrong place for it: the rules encoded
117
+ * here are not app preferences, they are the safety boundary that keeps an app
118
+ * from adopting a Folder a human created for something else. A third party
119
+ * re-deriving them from scratch gets the happy path right and the conflict
120
+ * cases wrong, and the failure is silent — the app happily writes into data it
121
+ * does not own.
122
+ *
123
+ * The contract, in one line: **an app owns a node only if it stamped it.**
124
+ * Ownership lives in `node.metadata` as `{ appId, resourceKey, schemaVersion }`.
125
+ * Anything else is either a legacy node this app plausibly created before
126
+ * stamping existed (claimable *only* after a full structural fingerprint match)
127
+ * or someone else's (never touched, always a `SETUP_CONFLICT`).
128
+ *
129
+ * This module is isomorphic — browser and Node both — and holds no I/O beyond
130
+ * the passed-in client.
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * import { createBusabaseClient } from "busabase-sdk";
135
+ * import { inspectProvisionedResources, provisionDeclaredResources } from "busabase-sdk/airapp";
136
+ *
137
+ * const client = createBusabaseClient({ baseUrl: window.location.origin });
138
+ * const config = {
139
+ * appId: "kelly-crm",
140
+ * appName: "Kelly CRM",
141
+ * schemaVersion: 1,
142
+ * folder: { slug: "kelly-crm", name: "Kelly CRM", description: "CRM workspace" },
143
+ * bases: [{ key: "contacts", slug: "kelly-crm-contacts-v1", name: "Contacts", fields: [...] }],
144
+ * };
145
+ *
146
+ * let resources = await inspectProvisionedResources(client, config);
147
+ * if (!resources.folder || resources.missing.length) {
148
+ * resources = await provisionDeclaredResources(client, config); // one idempotent ChangeRequest
149
+ * }
150
+ * ```
151
+ */
152
+ /**
3
153
  * A setup failure carrying its state as a `code`.
4
154
  *
5
155
  * `message` is deliberately kept in the historical `"CODE: detail"` shape: the
@@ -66,7 +216,7 @@ function resolveProvisionedFolder(folder, config) {
66
216
  airApp: null
67
217
  };
68
218
  if (folder.node?.type !== "folder" || folder.node?.slug !== config.folder.slug) throw setupError("SETUP_CONFLICT", `A different Folder already uses the slug ${config.folder.slug}; nothing was changed`);
69
- const rootOwned = hasResourceIdentity(folder.node, config.appId, "app-root");
219
+ const rootOwned = hasResourceIdentity(folder.node, config.appId, APP_ROOT_RESOURCE_KEY);
70
220
  const legacyRoot = hasEmptyMetadata(folder.node) && matchesDeclaration(folder.node, config.folder, "folder");
71
221
  if (!rootOwned && !legacyRoot) throw setupError("SETUP_CONFLICT", `The Folder ${config.folder.slug} does not belong to this app; nothing was changed`);
72
222
  const bases = [];
@@ -74,8 +224,8 @@ function resolveProvisionedFolder(folder, config) {
74
224
  const repairs = [];
75
225
  if (!ownsAppRoot(folder.node, config.appId, config.schemaVersion)) repairs.push({
76
226
  nodeId: folder.node.id,
77
- resourceKey: "app-root",
78
- metadata: resourceMetadata(config, "app-root")
227
+ resourceKey: APP_ROOT_RESOURCE_KEY,
228
+ metadata: resourceMetadata(config, APP_ROOT_RESOURCE_KEY)
79
229
  });
80
230
  for (const base of config.bases) {
81
231
  const matches = (folder.children ?? []).filter((node) => node.slug === base.slug);
@@ -139,7 +289,7 @@ function buildProvisionOperations(config, folder, missingBases) {
139
289
  slug: config.folder.slug,
140
290
  name: config.folder.name,
141
291
  description: config.folder.description ?? "",
142
- metadata: resourceMetadata(config, "app-root")
292
+ metadata: resourceMetadata(config, APP_ROOT_RESOURCE_KEY)
143
293
  });
144
294
  for (const base of missingBases) operations.push({
145
295
  kind: "create",
@@ -276,8 +276,7 @@ type OperationKind = GenericOperationKind | RegisteredOperationKind;
276
276
  * when a node carries no `icon`, every host falls back to the type icon, same
277
277
  * as before this field existed.
278
278
  *
279
- * The `attachment` variant mirrors buda's `NodeLogo` shape (see
280
- * `apps/buda/src/domains/agent-controller/components/use-logo-crop-upload.tsx`):
279
+ * The `attachment` variant follows the shared avatar-cropping model:
281
280
  * `url`/`attachmentId` are the CROPPED display image actually rendered, while
282
281
  * `originalUrl`/`originalAttachmentId` + `crop` are kept so the crop dialog can
283
282
  * re-open non-destructively against the untouched source image instead of
@@ -9257,6 +9256,431 @@ declare const cloudContract: {
9257
9256
  createdAt: z.ZodString;
9258
9257
  }, z.core.$strip>>;
9259
9258
  }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
9259
+ createBulkUpdateChangeRequest: import("@orpc/contract").ContractProcedure<z.ZodObject<{
9260
+ updates: z.ZodArray<z.ZodObject<{
9261
+ recordId: z.ZodString;
9262
+ fields: z.ZodRecord<z.ZodString, z.ZodUnknown>;
9263
+ baseCommitId: z.ZodOptional<z.ZodString>;
9264
+ message: z.ZodOptional<z.ZodString>;
9265
+ }, z.core.$strip>>;
9266
+ message: z.ZodDefault<z.ZodOptional<z.ZodString>>;
9267
+ submittedBy: z.ZodDefault<z.ZodOptional<z.ZodString>>;
9268
+ idempotencyKey: z.ZodOptional<z.ZodString>;
9269
+ autoMerge: z.ZodOptional<z.ZodBoolean>;
9270
+ baseId: z.ZodString;
9271
+ }, z.core.$strip>, z.ZodObject<{
9272
+ id: z.ZodString;
9273
+ baseId: z.ZodNullable<z.ZodString>;
9274
+ targetType: z.ZodEnum<{
9275
+ base: "base";
9276
+ node: "node";
9277
+ }>;
9278
+ nodeId: z.ZodNullable<z.ZodString>;
9279
+ status: z.ZodEnum<{
9280
+ abandoned: "abandoned";
9281
+ approved: "approved";
9282
+ changes_requested: "changes_requested";
9283
+ conflict: "conflict";
9284
+ in_review: "in_review";
9285
+ merged: "merged";
9286
+ rejected: "rejected";
9287
+ }>;
9288
+ submittedBy: z.ZodString;
9289
+ submittedByUser: z.ZodDefault<z.ZodOptional<z.ZodNullable<z.ZodObject<{
9290
+ id: z.ZodString;
9291
+ name: z.ZodNullable<z.ZodString>;
9292
+ email: z.ZodNullable<z.ZodString>;
9293
+ image: z.ZodNullable<z.ZodString>;
9294
+ role: z.ZodOptional<z.ZodNullable<z.ZodString>>;
9295
+ }, z.core.$strip>>>>;
9296
+ sourceMeta: z.ZodRecord<z.ZodString, z.ZodUnknown>;
9297
+ reviewPolicySnapshot: z.ZodRecord<z.ZodString, z.ZodUnknown>;
9298
+ mergeSummary: z.ZodRecord<z.ZodString, z.ZodUnknown>;
9299
+ rejectedReason: z.ZodNullable<z.ZodString>;
9300
+ reviewedAt: z.ZodNullable<z.ZodString>;
9301
+ mergedAt: z.ZodNullable<z.ZodString>;
9302
+ createdAt: z.ZodString;
9303
+ updatedAt: z.ZodString;
9304
+ base: z.ZodNullable<z.ZodObject<{
9305
+ id: z.ZodString;
9306
+ nodeId: z.ZodString;
9307
+ slug: z.ZodString;
9308
+ name: z.ZodString;
9309
+ description: z.ZodString;
9310
+ reviewPolicy: z.ZodObject<{
9311
+ kind: z.ZodLiteral<"single">;
9312
+ requiredApprovals: z.ZodNumber;
9313
+ }, z.core.$strip>;
9314
+ createdAt: z.ZodString;
9315
+ fields: z.ZodArray<z.ZodObject<{
9316
+ id: z.ZodString;
9317
+ baseId: z.ZodString;
9318
+ slug: z.ZodString;
9319
+ name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodEnum<{
9320
+ de: "de";
9321
+ en: "en";
9322
+ es: "es";
9323
+ fr: "fr";
9324
+ ja: "ja";
9325
+ ko: "ko";
9326
+ pt: "pt";
9327
+ "zh-CN": "zh-CN";
9328
+ "zh-TW": "zh-TW";
9329
+ }> & z.core.$partial, z.ZodString>]>;
9330
+ type: z.ZodEnum<{
9331
+ ai_summary: "ai_summary";
9332
+ ai_tags: "ai_tags";
9333
+ attachment: "attachment";
9334
+ auto_number: "auto_number";
9335
+ checkbox: "checkbox";
9336
+ code: "code";
9337
+ created_by: "created_by";
9338
+ created_time: "created_time";
9339
+ date: "date";
9340
+ email: "email";
9341
+ embed: "embed";
9342
+ formula: "formula";
9343
+ html: "html";
9344
+ json: "json";
9345
+ longtext: "longtext";
9346
+ lookup: "lookup";
9347
+ markdown: "markdown";
9348
+ multiselect: "multiselect";
9349
+ number: "number";
9350
+ phone: "phone";
9351
+ relation: "relation";
9352
+ select: "select";
9353
+ text: "text";
9354
+ updated_by: "updated_by";
9355
+ updated_time: "updated_time";
9356
+ url: "url";
9357
+ whiteboard: "whiteboard";
9358
+ yaml: "yaml";
9359
+ }>;
9360
+ required: z.ZodBoolean;
9361
+ position: z.ZodNumber;
9362
+ options: z.ZodDefault<z.ZodObject<{
9363
+ ai: z.ZodOptional<z.ZodObject<{
9364
+ model: z.ZodOptional<z.ZodString>;
9365
+ prompt: z.ZodOptional<z.ZodString>;
9366
+ reviewRequired: z.ZodOptional<z.ZodBoolean>;
9367
+ sourceFieldIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
9368
+ }, z.core.$strip>>;
9369
+ attachment: z.ZodOptional<z.ZodObject<{
9370
+ maxFiles: z.ZodOptional<z.ZodNumber>;
9371
+ allowedMimeTypes: z.ZodOptional<z.ZodArray<z.ZodString>>;
9372
+ maxFileSize: z.ZodOptional<z.ZodNumber>;
9373
+ }, z.core.$strip>>;
9374
+ choices: z.ZodOptional<z.ZodArray<z.ZodObject<{
9375
+ color: z.ZodOptional<z.ZodString>;
9376
+ id: z.ZodString;
9377
+ name: z.ZodString;
9378
+ }, z.core.$strip>>>;
9379
+ code: z.ZodOptional<z.ZodObject<{
9380
+ language: z.ZodOptional<z.ZodString>;
9381
+ }, z.core.$strip>>;
9382
+ embed: z.ZodOptional<z.ZodObject<{
9383
+ aspectRatio: z.ZodOptional<z.ZodEnum<{
9384
+ "16:9": "16:9";
9385
+ "1:1": "1:1";
9386
+ "4:3": "4:3";
9387
+ }>>;
9388
+ height: z.ZodOptional<z.ZodNumber>;
9389
+ providers: z.ZodOptional<z.ZodArray<z.ZodString>>;
9390
+ }, z.core.$strip>>;
9391
+ formula: z.ZodOptional<z.ZodObject<{
9392
+ expression: z.ZodString;
9393
+ }, z.core.$strip>>;
9394
+ inverseFieldId: z.ZodOptional<z.ZodString>;
9395
+ lookup: z.ZodOptional<z.ZodObject<{
9396
+ relationFieldSlug: z.ZodString;
9397
+ targetFieldSlug: z.ZodString;
9398
+ rollup: z.ZodOptional<z.ZodDefault<z.ZodEnum<{
9399
+ average: "average";
9400
+ concatenate: "concatenate";
9401
+ count: "count";
9402
+ max: "max";
9403
+ min: "min";
9404
+ sum: "sum";
9405
+ values: "values";
9406
+ }>>>;
9407
+ limit: z.ZodOptional<z.ZodEnum<{
9408
+ all: "all";
9409
+ first: "first";
9410
+ }>>;
9411
+ }, z.core.$strip>>;
9412
+ multiple: z.ZodOptional<z.ZodBoolean>;
9413
+ number: z.ZodOptional<z.ZodObject<{
9414
+ format: z.ZodOptional<z.ZodEnum<{
9415
+ currency: "currency";
9416
+ plain: "plain";
9417
+ }>>;
9418
+ currency: z.ZodOptional<z.ZodString>;
9419
+ locale: z.ZodOptional<z.ZodString>;
9420
+ }, z.core.$strip>>;
9421
+ targetBaseId: z.ZodOptional<z.ZodString>;
9422
+ targetBaseSlug: z.ZodOptional<z.ZodString>;
9423
+ }, z.core.$strip>>;
9424
+ }, z.core.$strip>>;
9425
+ }, z.core.$strip>>;
9426
+ node: z.ZodNullable<z.ZodType<NodeOutput, unknown, z.core.$ZodTypeInternals<NodeOutput, unknown>>>;
9427
+ operations: z.ZodArray<z.ZodObject<{
9428
+ id: z.ZodString;
9429
+ changeRequestId: z.ZodString;
9430
+ baseId: z.ZodNullable<z.ZodString>;
9431
+ targetType: z.ZodEnum<{
9432
+ base: "base";
9433
+ node: "node";
9434
+ }>;
9435
+ nodeId: z.ZodNullable<z.ZodString>;
9436
+ operation: z.ZodEnum<{
9437
+ [x: `${string}_file_create`]: `${string}_file_create`;
9438
+ [x: `${string}_file_delete`]: `${string}_file_delete`;
9439
+ [x: `${string}_file_update`]: `${string}_file_update`;
9440
+ [x: `${string}_metadata_update`]: `${string}_metadata_update`;
9441
+ base_add_field: "base_add_field";
9442
+ base_archive: "base_archive";
9443
+ base_convert_field: "base_convert_field";
9444
+ base_delete_field: "base_delete_field";
9445
+ base_reorder_fields: "base_reorder_fields";
9446
+ base_restore: "base_restore";
9447
+ base_restore_field: "base_restore_field";
9448
+ base_update_field: "base_update_field";
9449
+ doc_update: "doc_update";
9450
+ html_document_update: "html_document_update";
9451
+ node_create: "node_create";
9452
+ node_delete: "node_delete";
9453
+ node_move: "node_move";
9454
+ node_rename: "node_rename";
9455
+ node_restore: "node_restore";
9456
+ record_create: "record_create";
9457
+ record_delete: "record_delete";
9458
+ record_restore: "record_restore";
9459
+ record_update: "record_update";
9460
+ record_variant: "record_variant";
9461
+ view_create: "view_create";
9462
+ view_delete: "view_delete";
9463
+ view_restore: "view_restore";
9464
+ view_update: "view_update";
9465
+ whiteboard_document_update: "whiteboard_document_update";
9466
+ workflow_document_update: "workflow_document_update";
9467
+ }>;
9468
+ status: z.ZodEnum<{
9469
+ archived: "archived";
9470
+ failed: "failed";
9471
+ merged: "merged";
9472
+ pending: "pending";
9473
+ }>;
9474
+ targetRecordId: z.ZodNullable<z.ZodString>;
9475
+ targetViewId: z.ZodNullable<z.ZodString>;
9476
+ filePath: z.ZodNullable<z.ZodString>;
9477
+ sourceRecordId: z.ZodNullable<z.ZodString>;
9478
+ sourceCommitId: z.ZodNullable<z.ZodString>;
9479
+ baseCommitId: z.ZodNullable<z.ZodString>;
9480
+ headCommitId: z.ZodString;
9481
+ deleteMode: z.ZodEnum<{
9482
+ archive: "archive";
9483
+ }>;
9484
+ mergedRecordId: z.ZodNullable<z.ZodString>;
9485
+ mergedViewId: z.ZodNullable<z.ZodString>;
9486
+ position: z.ZodNumber;
9487
+ createdAt: z.ZodString;
9488
+ updatedAt: z.ZodString;
9489
+ headCommit: z.ZodObject<{
9490
+ id: z.ZodString;
9491
+ baseId: z.ZodNullable<z.ZodString>;
9492
+ targetType: z.ZodEnum<{
9493
+ base: "base";
9494
+ node: "node";
9495
+ }>;
9496
+ nodeId: z.ZodNullable<z.ZodString>;
9497
+ operationId: z.ZodNullable<z.ZodString>;
9498
+ parentCommitId: z.ZodNullable<z.ZodString>;
9499
+ payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
9500
+ operation: z.ZodEnum<{
9501
+ [x: `${string}_file_create`]: `${string}_file_create`;
9502
+ [x: `${string}_file_delete`]: `${string}_file_delete`;
9503
+ [x: `${string}_file_update`]: `${string}_file_update`;
9504
+ [x: `${string}_metadata_update`]: `${string}_metadata_update`;
9505
+ base_add_field: "base_add_field";
9506
+ base_archive: "base_archive";
9507
+ base_convert_field: "base_convert_field";
9508
+ base_delete_field: "base_delete_field";
9509
+ base_reorder_fields: "base_reorder_fields";
9510
+ base_restore: "base_restore";
9511
+ base_restore_field: "base_restore_field";
9512
+ base_update_field: "base_update_field";
9513
+ doc_update: "doc_update";
9514
+ html_document_update: "html_document_update";
9515
+ node_create: "node_create";
9516
+ node_delete: "node_delete";
9517
+ node_move: "node_move";
9518
+ node_rename: "node_rename";
9519
+ node_restore: "node_restore";
9520
+ record_create: "record_create";
9521
+ record_delete: "record_delete";
9522
+ record_restore: "record_restore";
9523
+ record_update: "record_update";
9524
+ record_variant: "record_variant";
9525
+ view_create: "view_create";
9526
+ view_delete: "view_delete";
9527
+ view_restore: "view_restore";
9528
+ view_update: "view_update";
9529
+ whiteboard_document_update: "whiteboard_document_update";
9530
+ workflow_document_update: "workflow_document_update";
9531
+ }>;
9532
+ message: z.ZodString;
9533
+ author: z.ZodString;
9534
+ authorUser: z.ZodDefault<z.ZodOptional<z.ZodNullable<z.ZodObject<{
9535
+ id: z.ZodString;
9536
+ name: z.ZodNullable<z.ZodString>;
9537
+ email: z.ZodNullable<z.ZodString>;
9538
+ image: z.ZodNullable<z.ZodString>;
9539
+ role: z.ZodOptional<z.ZodNullable<z.ZodString>>;
9540
+ }, z.core.$strip>>>>;
9541
+ createdAt: z.ZodString;
9542
+ }, z.core.$strip>;
9543
+ baseFields: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
9544
+ }, z.core.$strip>>;
9545
+ primaryOperation: z.ZodNullable<z.ZodObject<{
9546
+ id: z.ZodString;
9547
+ changeRequestId: z.ZodString;
9548
+ baseId: z.ZodNullable<z.ZodString>;
9549
+ targetType: z.ZodEnum<{
9550
+ base: "base";
9551
+ node: "node";
9552
+ }>;
9553
+ nodeId: z.ZodNullable<z.ZodString>;
9554
+ operation: z.ZodEnum<{
9555
+ [x: `${string}_file_create`]: `${string}_file_create`;
9556
+ [x: `${string}_file_delete`]: `${string}_file_delete`;
9557
+ [x: `${string}_file_update`]: `${string}_file_update`;
9558
+ [x: `${string}_metadata_update`]: `${string}_metadata_update`;
9559
+ base_add_field: "base_add_field";
9560
+ base_archive: "base_archive";
9561
+ base_convert_field: "base_convert_field";
9562
+ base_delete_field: "base_delete_field";
9563
+ base_reorder_fields: "base_reorder_fields";
9564
+ base_restore: "base_restore";
9565
+ base_restore_field: "base_restore_field";
9566
+ base_update_field: "base_update_field";
9567
+ doc_update: "doc_update";
9568
+ html_document_update: "html_document_update";
9569
+ node_create: "node_create";
9570
+ node_delete: "node_delete";
9571
+ node_move: "node_move";
9572
+ node_rename: "node_rename";
9573
+ node_restore: "node_restore";
9574
+ record_create: "record_create";
9575
+ record_delete: "record_delete";
9576
+ record_restore: "record_restore";
9577
+ record_update: "record_update";
9578
+ record_variant: "record_variant";
9579
+ view_create: "view_create";
9580
+ view_delete: "view_delete";
9581
+ view_restore: "view_restore";
9582
+ view_update: "view_update";
9583
+ whiteboard_document_update: "whiteboard_document_update";
9584
+ workflow_document_update: "workflow_document_update";
9585
+ }>;
9586
+ status: z.ZodEnum<{
9587
+ archived: "archived";
9588
+ failed: "failed";
9589
+ merged: "merged";
9590
+ pending: "pending";
9591
+ }>;
9592
+ targetRecordId: z.ZodNullable<z.ZodString>;
9593
+ targetViewId: z.ZodNullable<z.ZodString>;
9594
+ filePath: z.ZodNullable<z.ZodString>;
9595
+ sourceRecordId: z.ZodNullable<z.ZodString>;
9596
+ sourceCommitId: z.ZodNullable<z.ZodString>;
9597
+ baseCommitId: z.ZodNullable<z.ZodString>;
9598
+ headCommitId: z.ZodString;
9599
+ deleteMode: z.ZodEnum<{
9600
+ archive: "archive";
9601
+ }>;
9602
+ mergedRecordId: z.ZodNullable<z.ZodString>;
9603
+ mergedViewId: z.ZodNullable<z.ZodString>;
9604
+ position: z.ZodNumber;
9605
+ createdAt: z.ZodString;
9606
+ updatedAt: z.ZodString;
9607
+ headCommit: z.ZodObject<{
9608
+ id: z.ZodString;
9609
+ baseId: z.ZodNullable<z.ZodString>;
9610
+ targetType: z.ZodEnum<{
9611
+ base: "base";
9612
+ node: "node";
9613
+ }>;
9614
+ nodeId: z.ZodNullable<z.ZodString>;
9615
+ operationId: z.ZodNullable<z.ZodString>;
9616
+ parentCommitId: z.ZodNullable<z.ZodString>;
9617
+ payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
9618
+ operation: z.ZodEnum<{
9619
+ [x: `${string}_file_create`]: `${string}_file_create`;
9620
+ [x: `${string}_file_delete`]: `${string}_file_delete`;
9621
+ [x: `${string}_file_update`]: `${string}_file_update`;
9622
+ [x: `${string}_metadata_update`]: `${string}_metadata_update`;
9623
+ base_add_field: "base_add_field";
9624
+ base_archive: "base_archive";
9625
+ base_convert_field: "base_convert_field";
9626
+ base_delete_field: "base_delete_field";
9627
+ base_reorder_fields: "base_reorder_fields";
9628
+ base_restore: "base_restore";
9629
+ base_restore_field: "base_restore_field";
9630
+ base_update_field: "base_update_field";
9631
+ doc_update: "doc_update";
9632
+ html_document_update: "html_document_update";
9633
+ node_create: "node_create";
9634
+ node_delete: "node_delete";
9635
+ node_move: "node_move";
9636
+ node_rename: "node_rename";
9637
+ node_restore: "node_restore";
9638
+ record_create: "record_create";
9639
+ record_delete: "record_delete";
9640
+ record_restore: "record_restore";
9641
+ record_update: "record_update";
9642
+ record_variant: "record_variant";
9643
+ view_create: "view_create";
9644
+ view_delete: "view_delete";
9645
+ view_restore: "view_restore";
9646
+ view_update: "view_update";
9647
+ whiteboard_document_update: "whiteboard_document_update";
9648
+ workflow_document_update: "workflow_document_update";
9649
+ }>;
9650
+ message: z.ZodString;
9651
+ author: z.ZodString;
9652
+ authorUser: z.ZodDefault<z.ZodOptional<z.ZodNullable<z.ZodObject<{
9653
+ id: z.ZodString;
9654
+ name: z.ZodNullable<z.ZodString>;
9655
+ email: z.ZodNullable<z.ZodString>;
9656
+ image: z.ZodNullable<z.ZodString>;
9657
+ role: z.ZodOptional<z.ZodNullable<z.ZodString>>;
9658
+ }, z.core.$strip>>>>;
9659
+ createdAt: z.ZodString;
9660
+ }, z.core.$strip>;
9661
+ baseFields: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
9662
+ }, z.core.$strip>>;
9663
+ operationCount: z.ZodNumber;
9664
+ reviews: z.ZodArray<z.ZodObject<{
9665
+ id: z.ZodString;
9666
+ changeRequestId: z.ZodString;
9667
+ reviewerId: z.ZodString;
9668
+ reviewer: z.ZodDefault<z.ZodOptional<z.ZodNullable<z.ZodObject<{
9669
+ id: z.ZodString;
9670
+ name: z.ZodNullable<z.ZodString>;
9671
+ email: z.ZodNullable<z.ZodString>;
9672
+ image: z.ZodNullable<z.ZodString>;
9673
+ role: z.ZodOptional<z.ZodNullable<z.ZodString>>;
9674
+ }, z.core.$strip>>>>;
9675
+ verdict: z.ZodEnum<{
9676
+ approved: "approved";
9677
+ rejected: "rejected";
9678
+ }>;
9679
+ reason: z.ZodNullable<z.ZodString>;
9680
+ visibleOperationHeads: z.ZodRecord<z.ZodString, z.ZodString>;
9681
+ createdAt: z.ZodString;
9682
+ }, z.core.$strip>>;
9683
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
9260
9684
  createField: import("@orpc/contract").ContractProcedure<z.ZodObject<{
9261
9685
  name: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodEnum<{
9262
9686
  de: "de";
@@ -10635,6 +11059,7 @@ declare const cloudContract: {
10635
11059
  workspace: "workspace";
10636
11060
  }>>>;
10637
11061
  version: z.ZodDefault<z.ZodOptional<z.ZodString>>;
11062
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
10638
11063
  files: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
10639
11064
  path: z.ZodString;
10640
11065
  assetId: z.ZodString;
@@ -12957,7 +13382,10 @@ declare const cloudContract: {
12957
13382
  storageKey: z.ZodString;
12958
13383
  publicUrl: z.ZodString;
12959
13384
  }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
12960
- list: import("@orpc/contract").ContractProcedure<import("@orpc/contract").Schema<unknown, unknown>, z.ZodArray<z.ZodObject<{
13385
+ list: import("@orpc/contract").ContractProcedure<z.ZodOptional<z.ZodObject<{
13386
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
13387
+ cursor: z.ZodOptional<z.ZodString>;
13388
+ }, z.core.$strip>>, z.ZodArray<z.ZodObject<{
12961
13389
  id: z.ZodString;
12962
13390
  attachmentId: z.ZodString;
12963
13391
  name: z.ZodString;
@@ -13565,6 +13993,7 @@ declare const cloudContract: {
13565
13993
  }>;
13566
13994
  version: z.ZodDefault<z.ZodNullable<z.ZodString>>;
13567
13995
  available: z.ZodDefault<z.ZodBoolean>;
13996
+ comingSoon: z.ZodDefault<z.ZodBoolean>;
13568
13997
  unavailableReason: z.ZodDefault<z.ZodNullable<z.ZodString>>;
13569
13998
  connectionRequired: z.ZodDefault<z.ZodBoolean>;
13570
13999
  connectedAgentName: z.ZodDefault<z.ZodNullable<z.ZodString>>;
@@ -13579,6 +14008,37 @@ declare const cloudContract: {
13579
14008
  ok: z.ZodBoolean;
13580
14009
  deletedSessionCount: z.ZodNumber;
13581
14010
  }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
14011
+ connections: {
14012
+ list: import("@orpc/contract").ContractProcedure<import("@orpc/contract").Schema<unknown, unknown>, z.ZodArray<z.ZodObject<{
14013
+ slug: z.ZodString;
14014
+ agentName: z.ZodString;
14015
+ transport: z.ZodEnum<{
14016
+ "local-subprocess": "local-subprocess";
14017
+ "remote-websocket": "remote-websocket";
14018
+ }>;
14019
+ sessionCount: z.ZodNumber;
14020
+ latest: z.ZodNullable<z.ZodObject<{
14021
+ id: z.ZodString;
14022
+ slug: z.ZodString;
14023
+ agentName: z.ZodString;
14024
+ transport: z.ZodEnum<{
14025
+ "local-subprocess": "local-subprocess";
14026
+ "remote-websocket": "remote-websocket";
14027
+ }>;
14028
+ status: z.ZodEnum<{
14029
+ busy: "busy";
14030
+ connecting: "connecting";
14031
+ ended: "ended";
14032
+ failed: "failed";
14033
+ idle: "idle";
14034
+ waiting_permission: "waiting_permission";
14035
+ }>;
14036
+ createdAt: z.ZodString;
14037
+ lastActivityAt: z.ZodString;
14038
+ error: z.ZodDefault<z.ZodNullable<z.ZodString>>;
14039
+ }, z.core.$strip>>;
14040
+ }, z.core.$strip>>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
14041
+ };
13582
14042
  sessions: {
13583
14043
  list: import("@orpc/contract").ContractProcedure<import("@orpc/contract").Schema<unknown, unknown>, z.ZodArray<z.ZodObject<{
13584
14044
  id: z.ZodString;
@@ -14336,6 +14796,64 @@ declare const cloudContract: {
14336
14796
  warnings: z.ZodDefault<z.ZodArray<z.ZodString>>;
14337
14797
  }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
14338
14798
  };
14799
+ templates: {
14800
+ list: import("@orpc/contract").ContractProcedure<z.ZodDefault<z.ZodOptional<z.ZodObject<{
14801
+ refresh: z.ZodOptional<z.ZodBoolean>;
14802
+ }, z.core.$strip>>>, z.ZodObject<{
14803
+ templates: z.ZodArray<z.ZodObject<{
14804
+ id: z.ZodString;
14805
+ name: z.ZodString;
14806
+ description: z.ZodString;
14807
+ category: z.ZodString;
14808
+ tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
14809
+ screenshots: z.ZodDefault<z.ZodArray<z.ZodString>>;
14810
+ agentPrompts: z.ZodDefault<z.ZodArray<z.ZodString>>;
14811
+ version: z.ZodOptional<z.ZodString>;
14812
+ author: z.ZodOptional<z.ZodString>;
14813
+ license: z.ZodOptional<z.ZodString>;
14814
+ stats: z.ZodObject<{
14815
+ folders: z.ZodNumber;
14816
+ docs: z.ZodNumber;
14817
+ bases: z.ZodNumber;
14818
+ records: z.ZodNumber;
14819
+ files: z.ZodNumber;
14820
+ airapps: z.ZodNumber;
14821
+ skill: z.ZodBoolean;
14822
+ }, z.core.$strip>;
14823
+ install: z.ZodObject<{
14824
+ repoUrl: z.ZodString;
14825
+ intoFolder: z.ZodString;
14826
+ }, z.core.$strip>;
14827
+ sourceUrl: z.ZodString;
14828
+ }, z.core.$strip>>;
14829
+ repo: z.ZodString;
14830
+ ref: z.ZodString;
14831
+ error: z.ZodOptional<z.ZodString>;
14832
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
14833
+ };
14834
+ guides: {
14835
+ list: import("@orpc/contract").ContractProcedure<import("@orpc/contract").Schema<unknown, unknown>, z.ZodArray<z.ZodObject<{
14836
+ topic: z.ZodString;
14837
+ title: z.ZodString;
14838
+ kind: z.ZodEnum<{
14839
+ reference: "reference";
14840
+ walkthrough: "walkthrough";
14841
+ }>;
14842
+ summary: z.ZodString;
14843
+ }, z.core.$strip>>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
14844
+ read: import("@orpc/contract").ContractProcedure<z.ZodObject<{
14845
+ topic: z.ZodString;
14846
+ }, z.core.$strip>, z.ZodObject<{
14847
+ topic: z.ZodString;
14848
+ title: z.ZodString;
14849
+ kind: z.ZodEnum<{
14850
+ reference: "reference";
14851
+ walkthrough: "walkthrough";
14852
+ }>;
14853
+ content: z.ZodString;
14854
+ otherTopics: z.ZodArray<z.ZodString>;
14855
+ }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
14856
+ };
14339
14857
  changeRequests: {
14340
14858
  list: import("@orpc/contract").ContractProcedure<z.ZodDefault<z.ZodOptional<z.ZodObject<{
14341
14859
  limit: z.ZodDefault<z.ZodOptional<z.ZodCoercedNumber<unknown>>>;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as createBusabaseClient, c as cloudContract, d as NodeIconSchema, f as CREATABLE_NODE_TYPES, h as OperationKind, i as ResolvedConfig, l as NodeOutput, m as NodeType, n as BusabaseConfig, o as resolveConfig, p as CreatableNodeType, r as DEFAULT_BASE_URL, s as CloudContract, t as BusabaseClient, u as NodeIcon } from "./client-CbUceGzy.js";
1
+ import { a as createBusabaseClient, c as cloudContract, d as NodeIconSchema, f as CREATABLE_NODE_TYPES, h as OperationKind, i as ResolvedConfig, l as NodeOutput, m as NodeType, n as BusabaseConfig, o as resolveConfig, p as CreatableNodeType, r as DEFAULT_BASE_URL, s as CloudContract, t as BusabaseClient, u as NodeIcon } from "./client-DPyDyOdK.js";
2
2
  import { z } from "zod";
3
3
  //#region src/url.d.ts
4
4
  /**
package/dist/index.js CHANGED
@@ -86,6 +86,8 @@ const AgentCatalogEntryVOSchema = z.object({
86
86
  version: z.string().nullable().default(null),
87
87
  /** Whether this entry can be launched right now (binary present / URL configured). */
88
88
  available: z.boolean().default(false),
89
+ /** Whether this integration is listed for discovery but not available yet. */
90
+ comingSoon: z.boolean().default(false),
89
91
  /** Human-readable reason when `available` is false — never a bare "failed". */
90
92
  unavailableReason: z.string().nullable().default(null),
91
93
  connectionRequired: z.boolean().default(false),
@@ -134,6 +136,14 @@ const AgentSessionVOSchema = z.object({
134
136
  /** Set when status is "failed"; surfaced verbatim to the user. */
135
137
  error: z.string().nullable().default(null)
136
138
  });
139
+ /** One connected agent backend in the current space and authenticated user's scope. */
140
+ const AgentConnectionVOSchema = z.object({
141
+ slug: z.string(),
142
+ agentName: z.string(),
143
+ transport: AgentTransportSchema,
144
+ sessionCount: z.number().int().nonnegative(),
145
+ latest: AgentSessionVOSchema.nullable()
146
+ });
137
147
  /**
138
148
  * One streamed event from a session.
139
149
  *
@@ -208,6 +218,9 @@ const agentsContract = {
208
218
  ok: z.boolean(),
209
219
  deletedSessionCount: z.number().int().nonnegative()
210
220
  })),
221
+ connections: {
222
+ /** Connected backends, scoped to the current space and authenticated user. */
223
+ list: oc.output(AgentConnectionVOSchema.array()) },
211
224
  sessions: {
212
225
  list: oc.output(AgentSessionVOSchema.array()),
213
226
  create: oc.input(CreateAgentSessionInputSchema).output(AgentSessionVOSchema),
@@ -1015,8 +1028,7 @@ Object.fromEntries(ALL_OPERATIONS.map((operation) => [operation.kind, {
1015
1028
  * when a node carries no `icon`, every host falls back to the type icon, same
1016
1029
  * as before this field existed.
1017
1030
  *
1018
- * The `attachment` variant mirrors buda's `NodeLogo` shape (see
1019
- * `apps/buda/src/domains/agent-controller/components/use-logo-crop-upload.tsx`):
1031
+ * The `attachment` variant follows the shared avatar-cropping model:
1020
1032
  * `url`/`attachmentId` are the CROPPED display image actually rendered, while
1021
1033
  * `originalUrl`/`originalAttachmentId` + `crop` are kept so the crop dialog can
1022
1034
  * re-open non-destructively against the untouched source image instead of
@@ -1616,6 +1628,17 @@ const createFileTreeInputSchema = z.object({
1616
1628
  "public"
1617
1629
  ]).optional().default("private"),
1618
1630
  version: z.string().optional().default("0.1.0"),
1631
+ /**
1632
+ * Extra node metadata, stored alongside the server-owned keys.
1633
+ *
1634
+ * Rides along the change request on the review-first path, so it lands when a
1635
+ * human merges. That is the whole point: an ownership stamp applied only
1636
+ * after an immediate create would silently never be applied to a node that
1637
+ * was proposed instead — leaving the app unable to recognise its own
1638
+ * resources on the DEFAULT install path. Server-owned keys (`visibility`,
1639
+ * `version`) always win, so a caller cannot use this to rewrite them.
1640
+ */
1641
+ metadata: z.record(z.string(), z.unknown()).optional(),
1619
1642
  files: z.array(z.union([assetFileInputSchema, textFileInputSchema])).optional().default([]),
1620
1643
  autoMerge: z.boolean().optional(),
1621
1644
  mergeMode: z.enum(["merge", "replace"]).optional().default("merge")
@@ -1857,6 +1880,23 @@ const AssetVOSchema = z.object({
1857
1880
  textStatus: AssetTextStatusSchema,
1858
1881
  createdAt: z.string()
1859
1882
  });
1883
+ /**
1884
+ * Optional bounds on `GET /assets`.
1885
+ *
1886
+ * The route took no input at all, so it always returned EVERY asset in the
1887
+ * space — 6.16 MB for 10,025 assets on a real workspace, which is more than an
1888
+ * agent's whole context and more than a mobile client should ever download.
1889
+ *
1890
+ * Both fields are optional and the response shape is unchanged, so an existing
1891
+ * caller that passes nothing still gets the full array it always got. Pass
1892
+ * `limit` to page: assets come back newest-first, and `cursor` is the `id` of
1893
+ * the last asset from the previous page. A page shorter than `limit` (including
1894
+ * an empty one) means there are no more.
1895
+ */
1896
+ const ListAssetsInputSchema = z.object({
1897
+ limit: z.coerce.number().int().min(1).max(200).optional().describe("Page size, 1-200. Omit to return every asset (the historical behaviour)."),
1898
+ cursor: z.string().optional().describe("Asset id of the last row of the previous page. Requires `limit`.")
1899
+ }).optional();
1860
1900
  /** One place an asset is referenced — the row behind "Where Used". */
1861
1901
  const AssetUsageVOSchema = z.object({
1862
1902
  ownerType: z.enum([
@@ -2061,8 +2101,8 @@ const assetsContract = {
2061
2101
  path: "/assets",
2062
2102
  tags: ["Assets"],
2063
2103
  summary: "List assets",
2064
- successDescription: "Every asset in the space, with file metadata and usage counts."
2065
- }).output(z.array(AssetVOSchema)),
2104
+ successDescription: "Assets in the space, newest first, with file metadata and usage counts. Every asset when `limit` is omitted; otherwise one page, where `cursor` is the previous page's last asset id and a short page means the end."
2105
+ }).input(ListAssetsInputSchema).output(z.array(AssetVOSchema)),
2066
2106
  get: oc.route({
2067
2107
  method: "GET",
2068
2108
  path: "/assets/{assetId}",
@@ -2354,6 +2394,33 @@ const createBulkChangeRequestInputSchema = z.object({
2354
2394
  idempotencyKey: z.string().optional().describe("Optional client-supplied key that dedupes retries. Scoped per base + submitter: calling this endpoint again with the SAME idempotencyKey returns the bulk change request created by the first call instead of creating a duplicate. Omit for normal one-shot calls; only set it when you might retry."),
2355
2395
  autoMerge: z.boolean().optional()
2356
2396
  });
2397
+ const bulkRecordUpdateSchema = z.object({
2398
+ recordId: z.string().min(1),
2399
+ fields: z.record(z.string(), z.unknown()).refine((fields) => Object.keys(fields).length > 0, "fields must contain at least one field"),
2400
+ baseCommitId: z.string().min(1).optional(),
2401
+ message: z.string().min(1).optional()
2402
+ });
2403
+ const createBulkUpdateChangeRequestInputSchema = z.object({
2404
+ updates: z.array(bulkRecordUpdateSchema).min(1).max(1e3),
2405
+ message: z.string().optional().default("Bulk update records"),
2406
+ submittedBy: z.string().optional().default("local-producer"),
2407
+ idempotencyKey: z.string().optional(),
2408
+ autoMerge: z.boolean().optional()
2409
+ }).superRefine(({ updates }, ctx) => {
2410
+ const seen = /* @__PURE__ */ new Set();
2411
+ for (const [index, update] of updates.entries()) {
2412
+ if (seen.has(update.recordId)) ctx.addIssue({
2413
+ code: "custom",
2414
+ path: [
2415
+ "updates",
2416
+ index,
2417
+ "recordId"
2418
+ ],
2419
+ message: `Duplicate recordId in batch: ${update.recordId}`
2420
+ });
2421
+ seen.add(update.recordId);
2422
+ }
2423
+ });
2357
2424
  const recordFieldFilterInputSchema = z.object({
2358
2425
  baseId: z.string().optional(),
2359
2426
  fieldSlug: z.string().min(1),
@@ -2463,6 +2530,13 @@ const baseContract = {
2463
2530
  summary: "Create bulk record Change Request in Base",
2464
2531
  successDescription: "Created one change request proposing many record creates."
2465
2532
  }).input(createBulkChangeRequestInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
2533
+ createBulkUpdateChangeRequest: oc.route({
2534
+ method: "POST",
2535
+ path: "/bases/{baseId}/records/bulk-update-change-request",
2536
+ tags: ["Bases", "Change Requests"],
2537
+ summary: "Create bulk record update Change Request in Base",
2538
+ successDescription: "Created one change request proposing many record updates."
2539
+ }).input(createBulkUpdateChangeRequestInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
2466
2540
  createField: oc.route({
2467
2541
  method: "POST",
2468
2542
  path: "/bases/{baseId}/fields",
@@ -2949,6 +3023,55 @@ const formContract = {
2949
3023
  }).input(SubmitFormInputSchema.extend({ nodeId: z.string() })).output(FormSubmitResultSchema)
2950
3024
  };
2951
3025
  //#endregion
3026
+ //#region ../../packages/busabase-contract/src/domains/guides/types.ts
3027
+ /**
3028
+ * Guides — the operating manual, as VOs.
3029
+ *
3030
+ * The manual existed only over MCP: `instructions`, `resources/*` and the
3031
+ * `busabase_guide` tool. Anything driving the REST API — busabase-cli, a script,
3032
+ * a CI step, an AirApp — was told nothing about the approval-first rules, the
3033
+ * field types, or the AirApp runtime contract, and was then judged on whether it
3034
+ * guessed the house rules. These routes publish the same documents, built from
3035
+ * the same source, so the two surfaces cannot drift.
3036
+ */
3037
+ const GuideKindSchema = z.enum(["reference", "walkthrough"]);
3038
+ /** One catalog entry — enough to choose a topic without fetching every document. */
3039
+ const GuideTopicVOSchema = z.object({
3040
+ topic: z.string(),
3041
+ title: z.string(),
3042
+ /** `reference` = read it and apply it. `walkthrough` = a workflow to run WITH the user. */
3043
+ kind: GuideKindSchema,
3044
+ summary: z.string()
3045
+ });
3046
+ const GuideVOSchema = z.object({
3047
+ topic: z.string(),
3048
+ title: z.string(),
3049
+ kind: GuideKindSchema,
3050
+ /** The document itself, markdown. */
3051
+ content: z.string(),
3052
+ /** The other topics this deployment serves, so one call is enough to keep going. */
3053
+ otherTopics: z.array(z.string())
3054
+ });
3055
+ const ReadGuideInputSchema = z.object({ topic: z.string().min(1).describe("Guide topic. Call `GET /guides` for the ones served here.") });
3056
+ //#endregion
3057
+ //#region ../../packages/busabase-contract/src/domains/guides/contract.ts
3058
+ const guidesContract = {
3059
+ list: oc.route({
3060
+ method: "GET",
3061
+ path: "/guides",
3062
+ tags: ["Guides"],
3063
+ summary: "List the guides this deployment serves",
3064
+ successDescription: "The guide catalog: topic, title, kind, and a one-line summary. Read one with `GET /guides/{topic}`."
3065
+ }).output(z.array(GuideTopicVOSchema)),
3066
+ read: oc.route({
3067
+ method: "GET",
3068
+ path: "/guides/{topic}",
3069
+ tags: ["Guides"],
3070
+ summary: "Read one guide",
3071
+ successDescription: "The full markdown document, plus the other topics served here. Read `workspace` before proposing changes and `airapp` before writing any AirApp file."
3072
+ }).input(ReadGuideInputSchema).output(GuideVOSchema)
3073
+ };
3074
+ //#endregion
2952
3075
  //#region ../../packages/busabase-contract/src/domains/install/types.ts
2953
3076
  /**
2954
3077
  * Install domain — DTO inputs and VO outputs for server-side "Install from
@@ -3146,6 +3269,92 @@ const installContract = {
3146
3269
  }).input(InstallFromGithubDTOSchema).output(InstallResultVOSchema)
3147
3270
  };
3148
3271
  //#endregion
3272
+ //#region ../../packages/busabase-contract/src/domains/templates/types.ts
3273
+ /**
3274
+ * Template Center catalog types (pure zod, client-safe).
3275
+ *
3276
+ * The catalog is the file `busabase-cli index` builds from a skills repository
3277
+ * — see `busabase-package/index-build`. It is re-declared here rather than
3278
+ * imported because that module is Node-only (it reads packages), and these
3279
+ * shapes are rendered in a browser.
3280
+ *
3281
+ * Spec: `apps/busabase/content/spec/template-center.md` §6.4.
3282
+ */
3283
+ const TemplateStatsVOSchema = z.object({
3284
+ folders: z.number().int(),
3285
+ docs: z.number().int(),
3286
+ bases: z.number().int(),
3287
+ records: z.number().int(),
3288
+ files: z.number().int(),
3289
+ airapps: z.number().int(),
3290
+ skill: z.boolean()
3291
+ });
3292
+ const TemplateCardVOSchema = z.object({
3293
+ /** Stable across a catalog: `<repo>/<subdir>`. What a route keys on. */
3294
+ id: z.string(),
3295
+ name: z.string(),
3296
+ description: z.string(),
3297
+ category: z.string(),
3298
+ tags: z.array(z.string()).default([]),
3299
+ /**
3300
+ * Absolute URLs, resolved server-side.
3301
+ *
3302
+ * The catalog stores package-relative paths; turning them into URLs needs to
3303
+ * know the repo and ref, which the server already has and the browser would
3304
+ * otherwise have to re-derive. Doing it once here also means a card cannot
3305
+ * accidentally point at a different ref than the one it installs.
3306
+ */
3307
+ screenshots: z.array(z.string()).default([]),
3308
+ agentPrompts: z.array(z.string()).default([]),
3309
+ version: z.string().optional(),
3310
+ author: z.string().optional(),
3311
+ license: z.string().optional(),
3312
+ stats: TemplateStatsVOSchema,
3313
+ /** Exactly what the install dialog needs — no URL assembly in the client. */
3314
+ install: z.object({
3315
+ repoUrl: z.string(),
3316
+ intoFolder: z.string()
3317
+ }),
3318
+ /** Where a curious user goes to read it before installing. */
3319
+ sourceUrl: z.string()
3320
+ });
3321
+ const TemplateCatalogVOSchema = z.object({
3322
+ templates: z.array(TemplateCardVOSchema),
3323
+ /** `owner/repo` and ref the catalog was built from — shown as provenance. */
3324
+ repo: z.string(),
3325
+ ref: z.string(),
3326
+ /**
3327
+ * Why the catalog is empty or stale, in the server's own words.
3328
+ *
3329
+ * A gallery that silently shows nothing is indistinguishable from one that is
3330
+ * broken, and the difference matters: "the catalog could not be fetched" is a
3331
+ * thing a user can act on, "no templates" is not.
3332
+ */
3333
+ error: z.string().optional()
3334
+ });
3335
+ const ListTemplatesDTOSchema = z.object({
3336
+ /** Bypass the cache — the refresh button. */
3337
+ refresh: z.boolean().optional() }).optional().default({});
3338
+ //#endregion
3339
+ //#region ../../packages/busabase-contract/src/domains/templates/contract.ts
3340
+ /**
3341
+ * Template Center — the catalog a user browses before installing.
3342
+ *
3343
+ * Read-only and server-side on purpose. The catalog lives in a GitHub
3344
+ * repository, and a browser fetching it directly would hit CORS, would have no
3345
+ * cache shared between users, and would let the page decide which host to trust.
3346
+ * Installing is NOT here: a card's button hands its URL to the existing
3347
+ * `install.*` routes, so browsing and installing cannot disagree about what a
3348
+ * package is or who is allowed to install it.
3349
+ */
3350
+ const templatesContract = { list: oc.route({
3351
+ method: "GET",
3352
+ path: "/templates",
3353
+ tags: ["Templates"],
3354
+ summary: "List the Template Center catalog",
3355
+ successDescription: "The templates this server's configured catalog publishes, with provenance and per-template stats. `error` is set when the catalog could not be fetched, so an empty gallery can say why."
3356
+ }).input(ListTemplatesDTOSchema).output(TemplateCatalogVOSchema) };
3357
+ //#endregion
3149
3358
  //#region ../../packages/busabase-contract/src/domains/vault/types.ts
3150
3359
  const VaultItemKeySchema = z.string().trim().min(1).max(128).regex(/^[A-Z_][A-Z0-9_]*$/, "Use uppercase letters, numbers, and underscores");
3151
3360
  const VaultItemValueSchema = z.string().max(8192);
@@ -4210,6 +4419,8 @@ const busabaseContractRoutes = {
4210
4419
  webhooks: webhookContract,
4211
4420
  dump: dumpContract,
4212
4421
  install: installContract,
4422
+ templates: templatesContract,
4423
+ guides: guidesContract,
4213
4424
  changeRequests: {
4214
4425
  list: oc.route({
4215
4426
  method: "GET",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busabase-sdk",
3
- "version": "0.18.0",
3
+ "version": "0.19.1",
4
4
  "description": "Typed TypeScript/JavaScript SDK for the Busabase OpenAPI REST API. Talks to a local or remote `busabase server` (or Busabase Cloud).",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/busabase/busabase/tree/main/apps/busabase-sdk",
@@ -62,8 +62,8 @@
62
62
  "tsdown": "^0.22.14",
63
63
  "tsx": "^4.20.5",
64
64
  "typescript": "^7.0.2",
65
- "vitest": "^2.1.8",
66
- "busabase-contract": "0.18.0",
65
+ "vitest": "^4.1.11",
66
+ "busabase-contract": "0.19.1",
67
67
  "open-domains": "0.0.2",
68
68
  "openlib": "0.1.1"
69
69
  },