busabase-sdk 0.42.0 → 0.50.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/airapp.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as BusabaseClient } from "./client-vnydDLvt.js";
1
+ import { t as BusabaseClient } from "./client-EYjDfri3.js";
2
2
  //#region src/airapp.d.ts
3
3
  type NodeChangeRequestInput = Parameters<BusabaseClient["nodes"]["createChangeRequest"]>[0];
4
4
  type NodeOperationInput = NodeChangeRequestInput["operations"][number];
package/dist/airapp.js CHANGED
@@ -1,137 +1,4 @@
1
- import { z } from "zod";
2
- //#region ../../packages/busabase-contract/src/domains/skill/frontmatter.ts
3
- /**
4
- * `SKILL.md` frontmatter — the generic shape, independent of Busabase.
5
- *
6
- * This used to live inside `domains/package/template.ts`, which encoded an
7
- * assumption that no longer holds: that a Skill is a *part of* a template. Most
8
- * Skills are not. A Skill is a directory with a `SKILL.md` an agent reads; it
9
- * needs no `busabase.json`, ships no `content/`, and never installs into a
10
- * workspace. Busabase's own `busabase-app-creator` is one.
11
- *
12
- * Modelling it only as a template component meant there was nowhere to hang
13
- * checks for a plain Skill, so there were none. Hence its own file, in the
14
- * domain it belongs to.
15
- *
16
- * `metadata` is deliberately open: a Skill carries whatever its ecosystem
17
- * defines (categories, tags, risk labels, per-agent hints). The Busabase-specific
18
- * block that decides template-ness is layered on in `domains/package/template.ts`
19
- * — the template format knows about Skills, not the other way round.
20
- */
21
- const SkillFrontmatterSchema$1 = z.object({
22
- /** Identity. For a Skill inside a package this must equal the package name. */
23
- name: z.string().min(1),
24
- /**
25
- * How an agent decides whether to reach for this Skill at all — so an empty one
26
- * is not a cosmetic omission, it is a Skill that never gets picked.
27
- */
28
- description: z.string().default(""),
29
- metadata: z.object({}).passthrough().optional()
30
- });
31
- const TemplateAirAppRefSchema = z.object({
32
- /** Slug of the `content/<dir>` holding the AirApp. */
33
- slug: z.string().min(1),
34
- role: z.enum([
35
- "primary",
36
- "admin",
37
- "public",
38
- "tool"
39
- ]),
40
- label: z.string().optional()
41
- });
42
- /**
43
- * Secrets the app expects to find in the Vault.
44
- *
45
- * DECLARED, never created: the package format has no slot for secret values and
46
- * must not grow one (the same "you cannot leak what the format cannot express"
47
- * rule the whole format is built on). Install surfaces these as a post-install
48
- * prompt; the user fills them in the Vault themselves.
49
- */
50
- const TemplateSecretSchema = z.object({
51
- key: z.string().min(1),
52
- description: z.string().default(""),
53
- required: z.boolean().default(true)
54
- });
55
- z.object({
56
- /** Template Center category, e.g. `"crm"`, `"email"`, `"content"`. */
57
- category: z.string().min(1),
58
- tags: z.array(z.string()).default([]),
59
- /** Card/detail screenshots, package-relative (`assets/screenshots/overview.webp`). */
60
- screenshots: z.array(z.string()).default([]),
61
- /**
62
- * Ready-made prompts shown after install ("Ask agent" prefills the first).
63
- *
64
- * They are the difference between a folder of tables and something a user can
65
- * *use*: the point of a template is that the agent already knows the job, and
66
- * these are how that is made visible rather than left for the user to guess.
67
- */
68
- agentPrompts: z.array(z.string()).default([]),
69
- /** Single-AirApp shorthand. Mutually exclusive with `airapps`. */
70
- airapp: z.string().optional(),
71
- /** Multi-AirApp form. Exactly one entry must have `role: "primary"`. */
72
- airapps: z.array(TemplateAirAppRefSchema).optional(),
73
- /**
74
- * Bumped by the author when the declared resource shape changes.
75
- *
76
- * Part of the ownership stamp, so BOTH doors must agree on it: the installer
77
- * writes it, and a skill's own `setup.mjs` compares against it to decide
78
- * whether a node it finds is its own current shape or an older one to repair.
79
- * Defaulted rather than required so an author who never versions their app
80
- * still gets a stamp both sides recognise.
81
- */
82
- schemaVersion: z.number().int().nonnegative().default(1),
83
- vaultNamespace: z.string().optional(),
84
- secrets: z.array(TemplateSecretSchema).default([]),
85
- requires: z.object({ airapp: z.boolean().optional() }).default({})
86
- });
87
- /**
88
- * `metadata.busabase` inside the root `SKILL.md`'s YAML frontmatter.
89
- *
90
- * `template: true` is an EXPLICIT opt-in, not an inference from "this skill
91
- * happens to contain a package". Publishing a template means accepting that
92
- * installers will run its AirApp code and feed its SKILL.md to their agent; that
93
- * deserves a deliberate flag rather than a side effect of directory shape.
94
- */
95
- const SkillBusabaseMetadataSchema = z.object({
96
- template: z.boolean().default(false),
97
- folderSlug: z.string().optional(),
98
- /** Resource keys the manual talks about; each must exist under `content/`. */
99
- resources: z.array(z.string()).default([]),
100
- risk: z.string().optional()
101
- });
102
- SkillFrontmatterSchema$1.extend({ metadata: z.object({ busabase: SkillBusabaseMetadataSchema.optional() }).passthrough().optional() });
103
- /** Stamp on every resource node (Base, Drive, AirApp, …) an app owns. */
104
- const AppResourceOwnershipSchema = z.object({
105
- appId: z.string().min(1),
106
- /** Stable internal handle (`"contacts"`), NOT the installed slug. */
107
- resourceKey: z.string().min(1),
108
- schemaVersion: z.number().int().nonnegative()
109
- });
110
- /**
111
- * The `resourceKey` reserved for an app's root Folder.
112
- *
113
- * `busabase-sdk` recognises an app's own Folder by looking for exactly this
114
- * value (`ownsAppRoot`), so the installer must write it too — a Folder stamped
115
- * with anything else reads as a stranger's, and the skill's own `setup.mjs`
116
- * then refuses to touch its own workspace with `SETUP_CONFLICT`. Exported so
117
- * neither side carries the string literal privately.
118
- */
119
- const APP_ROOT_RESOURCE_KEY = "app-root";
120
- AppResourceOwnershipSchema.extend({
121
- resourceKey: z.literal(APP_ROOT_RESOURCE_KEY),
122
- version: z.string().optional(),
123
- source: z.object({
124
- repo: z.string().optional(),
125
- ref: z.string().optional(),
126
- subdir: z.string().optional()
127
- }).optional(),
128
- installedAt: z.string().optional()
129
- });
130
- z.object({
131
- appId: z.string().min(1),
132
- ["isTemplateSkill"]: z.literal(true)
133
- });
134
- //#endregion
1
+ import { t as APP_ROOT_RESOURCE_KEY } from "./template-C5oO6Au_.js";
135
2
  //#region src/airapp.ts
136
3
  /**
137
4
  * AirApp resource provisioning — how an app claims (or creates) the Folder and
@@ -14926,6 +14926,7 @@ declare const cloudContract: {
14926
14926
  "asset.uploaded": "asset.uploaded";
14927
14927
  changes_requested: "changes_requested";
14928
14928
  "record.created": "record.created";
14929
+ "record.updated": "record.updated";
14929
14930
  }>;
14930
14931
  enabled: z.ZodBoolean;
14931
14932
  createdBy: z.ZodString;
@@ -14953,6 +14954,7 @@ declare const cloudContract: {
14953
14954
  "asset.uploaded": "asset.uploaded";
14954
14955
  changes_requested: "changes_requested";
14955
14956
  "record.created": "record.created";
14957
+ "record.updated": "record.updated";
14956
14958
  }>;
14957
14959
  enabled: z.ZodBoolean;
14958
14960
  createdBy: z.ZodString;
@@ -14980,6 +14982,7 @@ declare const cloudContract: {
14980
14982
  "asset.uploaded": "asset.uploaded";
14981
14983
  changes_requested: "changes_requested";
14982
14984
  "record.created": "record.created";
14985
+ "record.updated": "record.updated";
14983
14986
  }>;
14984
14987
  enabled: z.ZodBoolean;
14985
14988
  createdBy: z.ZodString;
@@ -15009,6 +15012,7 @@ declare const cloudContract: {
15009
15012
  "asset.uploaded": "asset.uploaded";
15010
15013
  changes_requested: "changes_requested";
15011
15014
  "record.created": "record.created";
15015
+ "record.updated": "record.updated";
15012
15016
  }>;
15013
15017
  enabled: z.ZodBoolean;
15014
15018
  createdBy: z.ZodString;
@@ -15036,6 +15040,7 @@ declare const cloudContract: {
15036
15040
  "asset.uploaded": "asset.uploaded";
15037
15041
  changes_requested: "changes_requested";
15038
15042
  "record.created": "record.created";
15043
+ "record.updated": "record.updated";
15039
15044
  }>;
15040
15045
  enabled: z.ZodBoolean;
15041
15046
  createdBy: z.ZodString;
@@ -15063,6 +15068,7 @@ declare const cloudContract: {
15063
15068
  "asset.uploaded": "asset.uploaded";
15064
15069
  changes_requested: "changes_requested";
15065
15070
  "record.created": "record.created";
15071
+ "record.updated": "record.updated";
15066
15072
  }>;
15067
15073
  enabled: z.ZodBoolean;
15068
15074
  createdBy: z.ZodString;
@@ -15087,6 +15093,7 @@ declare const cloudContract: {
15087
15093
  "asset.uploaded": "asset.uploaded";
15088
15094
  changes_requested: "changes_requested";
15089
15095
  "record.created": "record.created";
15096
+ "record.updated": "record.updated";
15090
15097
  }>;
15091
15098
  baseId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
15092
15099
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -15103,6 +15110,7 @@ declare const cloudContract: {
15103
15110
  "asset.uploaded": "asset.uploaded";
15104
15111
  changes_requested: "changes_requested";
15105
15112
  "record.created": "record.created";
15113
+ "record.updated": "record.updated";
15106
15114
  }>;
15107
15115
  baseId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
15108
15116
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -15119,6 +15127,7 @@ declare const cloudContract: {
15119
15127
  "asset.uploaded": "asset.uploaded";
15120
15128
  changes_requested: "changes_requested";
15121
15129
  "record.created": "record.created";
15130
+ "record.updated": "record.updated";
15122
15131
  }>;
15123
15132
  baseId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
15124
15133
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -15137,6 +15146,7 @@ declare const cloudContract: {
15137
15146
  "asset.uploaded": "asset.uploaded";
15138
15147
  changes_requested: "changes_requested";
15139
15148
  "record.created": "record.created";
15149
+ "record.updated": "record.updated";
15140
15150
  }>;
15141
15151
  enabled: z.ZodBoolean;
15142
15152
  createdBy: z.ZodString;
@@ -15164,6 +15174,7 @@ declare const cloudContract: {
15164
15174
  "asset.uploaded": "asset.uploaded";
15165
15175
  changes_requested: "changes_requested";
15166
15176
  "record.created": "record.created";
15177
+ "record.updated": "record.updated";
15167
15178
  }>;
15168
15179
  enabled: z.ZodBoolean;
15169
15180
  createdBy: z.ZodString;
@@ -15191,6 +15202,7 @@ declare const cloudContract: {
15191
15202
  "asset.uploaded": "asset.uploaded";
15192
15203
  changes_requested: "changes_requested";
15193
15204
  "record.created": "record.created";
15205
+ "record.updated": "record.updated";
15194
15206
  }>;
15195
15207
  enabled: z.ZodBoolean;
15196
15208
  createdBy: z.ZodString;
@@ -15215,6 +15227,7 @@ declare const cloudContract: {
15215
15227
  "asset.uploaded": "asset.uploaded";
15216
15228
  changes_requested: "changes_requested";
15217
15229
  "record.created": "record.created";
15230
+ "record.updated": "record.updated";
15218
15231
  }>;
15219
15232
  baseId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
15220
15233
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -15232,6 +15245,7 @@ declare const cloudContract: {
15232
15245
  "asset.uploaded": "asset.uploaded";
15233
15246
  changes_requested: "changes_requested";
15234
15247
  "record.created": "record.created";
15248
+ "record.updated": "record.updated";
15235
15249
  }>;
15236
15250
  baseId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
15237
15251
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -15249,6 +15263,7 @@ declare const cloudContract: {
15249
15263
  "asset.uploaded": "asset.uploaded";
15250
15264
  changes_requested: "changes_requested";
15251
15265
  "record.created": "record.created";
15266
+ "record.updated": "record.updated";
15252
15267
  }>;
15253
15268
  baseId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
15254
15269
  enabled: z.ZodDefault<z.ZodBoolean>;
@@ -15268,6 +15283,7 @@ declare const cloudContract: {
15268
15283
  "asset.uploaded": "asset.uploaded";
15269
15284
  changes_requested: "changes_requested";
15270
15285
  "record.created": "record.created";
15286
+ "record.updated": "record.updated";
15271
15287
  }>;
15272
15288
  enabled: z.ZodBoolean;
15273
15289
  createdBy: z.ZodString;
@@ -15295,6 +15311,7 @@ declare const cloudContract: {
15295
15311
  "asset.uploaded": "asset.uploaded";
15296
15312
  changes_requested: "changes_requested";
15297
15313
  "record.created": "record.created";
15314
+ "record.updated": "record.updated";
15298
15315
  }>;
15299
15316
  enabled: z.ZodBoolean;
15300
15317
  createdBy: z.ZodString;
@@ -15322,6 +15339,7 @@ declare const cloudContract: {
15322
15339
  "asset.uploaded": "asset.uploaded";
15323
15340
  changes_requested: "changes_requested";
15324
15341
  "record.created": "record.created";
15342
+ "record.updated": "record.updated";
15325
15343
  }>;
15326
15344
  enabled: z.ZodBoolean;
15327
15345
  createdBy: z.ZodString;
@@ -15355,6 +15373,7 @@ declare const cloudContract: {
15355
15373
  "asset.uploaded": "asset.uploaded";
15356
15374
  changes_requested: "changes_requested";
15357
15375
  "record.created": "record.created";
15376
+ "record.updated": "record.updated";
15358
15377
  }>;
15359
15378
  status: z.ZodEnum<{
15360
15379
  failed: "failed";
@@ -15376,6 +15395,7 @@ declare const cloudContract: {
15376
15395
  "asset.uploaded": "asset.uploaded";
15377
15396
  changes_requested: "changes_requested";
15378
15397
  "record.created": "record.created";
15398
+ "record.updated": "record.updated";
15379
15399
  }>;
15380
15400
  status: z.ZodEnum<{
15381
15401
  failed: "failed";
@@ -15571,6 +15591,12 @@ declare const cloudContract: {
15571
15591
  name: z.ZodString;
15572
15592
  description: z.ZodString;
15573
15593
  category: z.ZodString;
15594
+ risk: z.ZodOptional<z.ZodEnum<{
15595
+ "gated-write": "gated-write";
15596
+ "local-write": "local-write";
15597
+ "read-only": "read-only";
15598
+ sandbox: "sandbox";
15599
+ }>>;
15574
15600
  tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
15575
15601
  screenshots: z.ZodDefault<z.ZodArray<z.ZodString>>;
15576
15602
  agentPrompts: z.ZodDefault<z.ZodArray<z.ZodString>>;
@@ -19476,6 +19502,31 @@ declare const cloudContract: {
19476
19502
  }>;
19477
19503
  value: z.ZodOptional<z.ZodUnknown>;
19478
19504
  }, z.core.$strip>>>;
19505
+ valueFilters: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
19506
+ fieldSlug: z.ZodString;
19507
+ operator: z.ZodEnum<{
19508
+ eq: "eq";
19509
+ gt: "gt";
19510
+ gte: "gte";
19511
+ lt: "lt";
19512
+ lte: "lte";
19513
+ ne: "ne";
19514
+ }>;
19515
+ value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodBoolean]>;
19516
+ }, z.core.$strip>, z.ZodObject<{
19517
+ any: z.ZodArray<z.ZodObject<{
19518
+ fieldSlug: z.ZodString;
19519
+ operator: z.ZodEnum<{
19520
+ eq: "eq";
19521
+ gt: "gt";
19522
+ gte: "gte";
19523
+ lt: "lt";
19524
+ lte: "lte";
19525
+ ne: "ne";
19526
+ }>;
19527
+ value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodBoolean]>;
19528
+ }, z.core.$strip>>;
19529
+ }, z.core.$strip>]>>>;
19479
19530
  sort: z.ZodOptional<z.ZodObject<{
19480
19531
  fieldSlug: z.ZodString;
19481
19532
  fieldType: z.ZodOptional<z.ZodString>;
@@ -19930,12 +19981,51 @@ declare const cloudContract: {
19930
19981
  }>;
19931
19982
  value: z.ZodOptional<z.ZodUnknown>;
19932
19983
  }, z.core.$strip>>>;
19984
+ valueFilters: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
19985
+ fieldSlug: z.ZodString;
19986
+ operator: z.ZodEnum<{
19987
+ eq: "eq";
19988
+ gt: "gt";
19989
+ gte: "gte";
19990
+ lt: "lt";
19991
+ lte: "lte";
19992
+ ne: "ne";
19993
+ }>;
19994
+ value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodBoolean]>;
19995
+ }, z.core.$strip>, z.ZodObject<{
19996
+ any: z.ZodArray<z.ZodObject<{
19997
+ fieldSlug: z.ZodString;
19998
+ operator: z.ZodEnum<{
19999
+ eq: "eq";
20000
+ gt: "gt";
20001
+ gte: "gte";
20002
+ lt: "lt";
20003
+ lte: "lte";
20004
+ ne: "ne";
20005
+ }>;
20006
+ value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodBoolean]>;
20007
+ }, z.core.$strip>>;
20008
+ }, z.core.$strip>]>>>;
19933
20009
  }, z.core.$strip>>>, z.ZodObject<{
19934
20010
  total: z.ZodNumber;
19935
20011
  }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
19936
20012
  groupBy: import("@orpc/contract").ContractProcedure<z.ZodObject<{
19937
20013
  baseId: z.ZodString;
19938
- fieldSlug: z.ZodString;
20014
+ fieldSlug: z.ZodOptional<z.ZodString>;
20015
+ bucketing: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
20016
+ grid: "grid";
20017
+ sql: "sql";
20018
+ }>>>;
20019
+ aggregates: z.ZodOptional<z.ZodArray<z.ZodObject<{
20020
+ fn: z.ZodEnum<{
20021
+ avg: "avg";
20022
+ count: "count";
20023
+ max: "max";
20024
+ min: "min";
20025
+ sum: "sum";
20026
+ }>;
20027
+ fieldSlug: z.ZodString;
20028
+ }, z.core.$strip>>>;
19939
20029
  viewId: z.ZodOptional<z.ZodString>;
19940
20030
  filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
19941
20031
  fieldSlug: z.ZodString;
@@ -19950,10 +20040,36 @@ declare const cloudContract: {
19950
20040
  }>;
19951
20041
  value: z.ZodOptional<z.ZodUnknown>;
19952
20042
  }, z.core.$strip>>>;
20043
+ valueFilters: z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
20044
+ fieldSlug: z.ZodString;
20045
+ operator: z.ZodEnum<{
20046
+ eq: "eq";
20047
+ gt: "gt";
20048
+ gte: "gte";
20049
+ lt: "lt";
20050
+ lte: "lte";
20051
+ ne: "ne";
20052
+ }>;
20053
+ value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodBoolean]>;
20054
+ }, z.core.$strip>, z.ZodObject<{
20055
+ any: z.ZodArray<z.ZodObject<{
20056
+ fieldSlug: z.ZodString;
20057
+ operator: z.ZodEnum<{
20058
+ eq: "eq";
20059
+ gt: "gt";
20060
+ gte: "gte";
20061
+ lt: "lt";
20062
+ lte: "lte";
20063
+ ne: "ne";
20064
+ }>;
20065
+ value: z.ZodUnion<readonly [z.ZodNumber, z.ZodString, z.ZodBoolean]>;
20066
+ }, z.core.$strip>>;
20067
+ }, z.core.$strip>]>>>;
19953
20068
  }, z.core.$strip>, z.ZodObject<{
19954
20069
  groups: z.ZodArray<z.ZodObject<{
19955
- value: z.ZodNullable<z.ZodString>;
20070
+ value: z.ZodNullable<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
19956
20071
  count: z.ZodNumber;
20072
+ aggregates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNullable<z.ZodNumber>>>;
19957
20073
  }, z.core.$strip>>;
19958
20074
  total: z.ZodNumber;
19959
20075
  }, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as AssetUsageVO, A as NodeVO, At as ViewConfigVO, B as UpdateVaultSettingsDTO, Bt as NodeIconSchema, C as CommentVO, Ct as GalleryCardSize, D as MentionInboxItemVO, Dt as RecordVO, E as LookupRollup, Et as RecordLinkVO, F as SearchResponseVO, Ft as ViewVO, G as VaultItemVO, H as VaultEnvironment, Ht as CreatableNodeType, I as SearchResultKind, It as AttachmentRef, J as VaultSettingsVO, K as VaultRuntimeEnv, L as SearchResultVO, Lt as CloudContract, M as OperationVO, Mt as ViewFilterVO, N as ReviewVO, Nt as ViewSortVO, O as MentionInboxPageVO, Ot as VIEW_FIELD_MAX_WIDTH, P as ReviewVerdict, Pt as ViewType, Q as AssetTextStatus, R as SourceAttributionVO, Rt as cloudContract, S as CommentSubjectType, St as BaseVO, T as FieldType, Tt as GanttScale, U as VaultItemInput, Ut as NodeType, V as VaultAccessPolicy, Vt as CREATABLE_NODE_TYPES, W as VaultItemKind, Wt as OperationKind, X as FileNodeVO, Y as FileNodeMetadata, Z as AssetDetailVO, _ as ChangeRequestVO, _t as ListFormsVO, a as createBusabaseClient, at as FileTreeReadFileVO, b as CommentMentionTargetType, bt as AssetAttachmentRef, c as AuditAction, ct as CreateFormDTO, d as ChangeRequestBatchFailureVO, dt as FormPageSourceVO, et as AssetVO, f as ChangeRequestCountsVO, ft as FormShareVO, g as ChangeRequestTargetType, gt as ListFormsDTO, h as ChangeRequestStatus, ht as FormVO, i as ResolvedConfig, it as FileTreeNodeVO, j as OperationStatus, jt as ViewFilterOperator, k as NodeSearchResultVO, kt as VIEW_FIELD_MIN_WIDTH, l as AuditEventVO, lt as FormBoundFieldVO, m as ChangeRequestReviewBatchResultVO, mt as FormThemeVO, n as BusabaseConfig, nt as GrepResultVO, o as resolveConfig, ot as NodeDetailVO, p as ChangeRequestMergeBatchResultVO, pt as FormSubmitResultVO, q as VaultScopeType, r as DEFAULT_BASE_URL, rt as FileTreeFileVO, s as AgentTaskVO, st as ActivityItemVO, t as BusabaseClient, tt as GrepInputDTO, u as BusabaseSourceChannel, ut as FormFieldBindingVO, v as CommentMentionDispatchStatus, vt as SubmitFormDTO, w as CommitVO, wt as GalleryCoverFit, x as CommentMentionVO, xt as BaseFieldVO, y as CommentMentionInputDTO, yt as UpdateFormDTO, z as UserRefVO, zt as NodeIcon } from "./client-vnydDLvt.js";
1
+ import { $ as AssetUsageVO, A as NodeVO, At as ViewConfigVO, B as UpdateVaultSettingsDTO, Bt as NodeIconSchema, C as CommentVO, Ct as GalleryCardSize, D as MentionInboxItemVO, Dt as RecordVO, E as LookupRollup, Et as RecordLinkVO, F as SearchResponseVO, Ft as ViewVO, G as VaultItemVO, H as VaultEnvironment, Ht as CreatableNodeType, I as SearchResultKind, It as AttachmentRef, J as VaultSettingsVO, K as VaultRuntimeEnv, L as SearchResultVO, Lt as CloudContract, M as OperationVO, Mt as ViewFilterVO, N as ReviewVO, Nt as ViewSortVO, O as MentionInboxPageVO, Ot as VIEW_FIELD_MAX_WIDTH, P as ReviewVerdict, Pt as ViewType, Q as AssetTextStatus, R as SourceAttributionVO, Rt as cloudContract, S as CommentSubjectType, St as BaseVO, T as FieldType, Tt as GanttScale, U as VaultItemInput, Ut as NodeType, V as VaultAccessPolicy, Vt as CREATABLE_NODE_TYPES, W as VaultItemKind, Wt as OperationKind, X as FileNodeVO, Y as FileNodeMetadata, Z as AssetDetailVO, _ as ChangeRequestVO, _t as ListFormsVO, a as createBusabaseClient, at as FileTreeReadFileVO, b as CommentMentionTargetType, bt as AssetAttachmentRef, c as AuditAction, ct as CreateFormDTO, d as ChangeRequestBatchFailureVO, dt as FormPageSourceVO, et as AssetVO, f as ChangeRequestCountsVO, ft as FormShareVO, g as ChangeRequestTargetType, gt as ListFormsDTO, h as ChangeRequestStatus, ht as FormVO, i as ResolvedConfig, it as FileTreeNodeVO, j as OperationStatus, jt as ViewFilterOperator, k as NodeSearchResultVO, kt as VIEW_FIELD_MIN_WIDTH, l as AuditEventVO, lt as FormBoundFieldVO, m as ChangeRequestReviewBatchResultVO, mt as FormThemeVO, n as BusabaseConfig, nt as GrepResultVO, o as resolveConfig, ot as NodeDetailVO, p as ChangeRequestMergeBatchResultVO, pt as FormSubmitResultVO, q as VaultScopeType, r as DEFAULT_BASE_URL, rt as FileTreeFileVO, s as AgentTaskVO, st as ActivityItemVO, t as BusabaseClient, tt as GrepInputDTO, u as BusabaseSourceChannel, ut as FormFieldBindingVO, v as CommentMentionDispatchStatus, vt as SubmitFormDTO, w as CommitVO, wt as GalleryCoverFit, x as CommentMentionVO, xt as BaseFieldVO, y as CommentMentionInputDTO, yt as UpdateFormDTO, z as UserRefVO, zt as NodeIcon } from "./client-EYjDfri3.js";
2
2
  import { z } from "zod";
3
3
  //#region src/url.d.ts
4
4
  /**
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { n as TemplateRiskLevelSchema } from "./template-C5oO6Au_.js";
1
2
  import { t as normalizeBaseUrl } from "./url-B8GMXalA.js";
2
3
  import { ORPCError, createORPCClient } from "@orpc/client";
3
4
  import { OpenAPILink } from "@orpc/openapi-client/fetch";
@@ -1222,12 +1223,7 @@ const nodeShareSchema = z.object({
1222
1223
  const listNodesInputSchema = z.object({
1223
1224
  parentId: z.string().nullable().optional().describe("Node to start from. Omit or null to start from the space root."),
1224
1225
  depth: z.coerce.number().int().min(1).max(5).optional().describe("How many levels beneath the start point to eagerly include (default 2 once either field is set). Capped at 5."),
1225
- /**
1226
- * `active` (default) walks the live tree. `archived` returns the flat set of
1227
- * soft-archived nodes for the Trash view — no `parentId`/`depth` walk, since
1228
- * archived nodes are shown as a list, not a tree.
1229
- */
1230
- status: z.enum(["active", "archived"]).optional().default("active"),
1226
+ status: z.enum(["active", "archived"]).optional().default("active").describe("`active` walks the live TREE. `archived` returns a FLAT list of soft-archived nodes (the Trash view) with no parent/depth walk — so the response shape you can rely on differs between the two, not just the rows."),
1231
1227
  /**
1232
1228
  * Narrow to specific node types and return a FLAT list of lightweight node
1233
1229
  * summaries (`children: []`) instead of walking the tree. This is what
@@ -1254,8 +1250,8 @@ const listNodesInputSchema = z.object({
1254
1250
  types: z.union([z.array(z.enum(NODE_TYPES)), z.enum(NODE_TYPES)]).transform((value) => Array.isArray(value) ? value : [value]).optional().describe("Return a flat list of lightweight summaries for these node types instead of the tree. Read one node's full detail with GET /nodes/{nodeId}.")
1255
1251
  }).optional();
1256
1252
  const isDescendantInputSchema = z.object({
1257
- nodeId: z.string(),
1258
- potentialAncestorId: z.string()
1253
+ nodeId: z.string().describe("The node walked UPWARDS from — the possible descendant."),
1254
+ potentialAncestorId: z.string().describe("The node looked for on the way up. Answers \"is `nodeId` inside this one?\", not the reverse — swapping the two silently returns the wrong answer rather than an error.")
1259
1255
  });
1260
1256
  const isDescendantOutputSchema = z.object({ isDescendant: z.boolean() });
1261
1257
  const updateNodeMetadataInputSchema = z.object({
@@ -1322,8 +1318,8 @@ const updateNodeAgentPromptsInputSchema = z.object({
1322
1318
  agentPrompts: customAgentPromptsSchema.nullable()
1323
1319
  });
1324
1320
  const searchNodesByNameInputSchema = z.object({
1325
- query: z.string().min(1),
1326
- limit: z.coerce.number().int().min(1).max(50).optional().default(20)
1321
+ query: z.string().min(1).describe("Matched against node NAMES only. Use `/api/v1/search` to search content."),
1322
+ limit: z.coerce.number().int().min(1).max(50).optional().default(20).describe("Results to return. Capped at 50 here, unlike most listings' 100.")
1327
1323
  });
1328
1324
  const nodeSearchResultSchema = z.object({
1329
1325
  id: z.string(),
@@ -1514,8 +1510,8 @@ const mentionInboxPageSchema = z.object({
1514
1510
  unreadCount: z.number().int()
1515
1511
  });
1516
1512
  const listMentionInboxInputSchema = z.object({
1517
- page: z.number().int().min(1).optional().default(1),
1518
- pageSize: z.number().int().min(1).max(100).optional().default(50)
1513
+ page: z.number().int().min(1).optional().default(1).describe("1-indexed, not 0-indexed."),
1514
+ pageSize: z.number().int().min(1).max(100).optional().default(50).describe("Mentions per page. Capped at 100.")
1519
1515
  });
1520
1516
  const markMentionsReadInputSchema = z.object({
1521
1517
  /** Stamps every unread mention row this caller has on that comment. */
@@ -1753,8 +1749,8 @@ const reviewChangeRequestInputSchema = z.object({
1753
1749
  reason: z.string().optional()
1754
1750
  });
1755
1751
  const commentSubjectInputSchema = z.object({
1756
- subjectType: commentSubjectTypeSchema,
1757
- subjectId: z.string().min(1)
1752
+ subjectType: commentSubjectTypeSchema.describe("What the comment thread hangs off, which decides how `subjectId` is interpreted."),
1753
+ subjectId: z.string().min(1).describe("The subject's id, interpreted according to `subjectType`.")
1758
1754
  });
1759
1755
  const createCommentInputSchema = commentSubjectInputSchema.extend({
1760
1756
  authorId: z.string().optional().default("local-admin"),
@@ -1766,34 +1762,33 @@ const createCommentInputSchema = commentSubjectInputSchema.extend({
1766
1762
  */
1767
1763
  mentions: z.array(commentMentionInputSchema).optional().default([])
1768
1764
  });
1769
- const listInputSchema = z.object({ limit: z.coerce.number().int().min(1).max(100).optional().default(50) }).optional().default({ limit: 50 });
1765
+ const listInputSchema = z.object({ limit: z.coerce.number().int().min(1).max(100).optional().default(50).describe("Rows to return, most recent first. Capped at 100; this listing has no cursor.") }).optional().default({ limit: 50 });
1770
1766
  /**
1771
1767
  * Active-or-archived selector for listings whose archived twin was folded in.
1772
1768
  * Archived rows are the same shape as live ones — the only thing that ever
1773
1769
  * differed between `/x` and `/x/archived` was this predicate.
1774
1770
  */
1775
- const listByStatusInputSchema = z.object({ status: z.enum(["active", "archived"]).optional().default("active") });
1771
+ const listByStatusInputSchema = z.object({ status: z.enum(["active", "archived"]).optional().default("active").describe("`active` (default) or the soft-archived set. Archived rows have the SAME shape as live ones — this is a predicate, not a different resource.") });
1776
1772
  const listChangeRequestsPagedInputSchema = z.object({
1777
- limit: z.coerce.number().int().min(1).max(100).optional().default(50),
1778
- /** Opaque base64 cursor (`createdAt|id`) for keyset pagination. */
1779
- cursor: z.string().optional(),
1780
- status: z.array(changeRequestStatusSchema).optional(),
1781
- mine: z.boolean().optional(),
1782
- affectsNodeId: z.string().min(1).optional()
1773
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50).describe("Change requests per page. Capped at 100; ask for the next page with `cursor`."),
1774
+ cursor: z.string().optional().describe("Opaque page cursor: pass back the `nextCursor` from the previous response. Do not construct or parse it."),
1775
+ status: z.array(changeRequestStatusSchema).optional().describe("Keep only these statuses. Omitting it returns every status, not just open ones."),
1776
+ mine: z.boolean().optional().describe("Only change requests CREATED by the acting user — not ones awaiting their review."),
1777
+ affectsNodeId: z.string().min(1).optional().describe("Only change requests whose target, or any of their operations, touches this node — including Base-backed nodes. To ask whether a resource already has an unfinished change request, pass this with `limit: 1` rather than paging the space: an empty result is conclusive.")
1783
1778
  }).optional().default({ limit: 50 });
1784
1779
  const listChangeRequestsResponseSchema = z.object({
1785
1780
  changeRequests: z.array(changeRequestSchema),
1786
1781
  nextCursor: z.string().nullable()
1787
1782
  });
1788
1783
  const changeRequestPageInputShape = {
1789
- page: z.coerce.number().int().min(1).optional().default(1),
1790
- pageSize: z.coerce.number().int().min(1).max(100).optional().default(50),
1791
- status: z.array(changeRequestStatusSchema).optional(),
1792
- mine: z.boolean().optional()
1784
+ page: z.coerce.number().int().min(1).optional().default(1).describe("1-indexed, not 0-indexed."),
1785
+ pageSize: z.coerce.number().int().min(1).max(100).optional().default(50).describe("Change requests per page. Capped at 100."),
1786
+ status: z.array(changeRequestStatusSchema).optional().describe("Keep only these statuses. Omitting it returns every status, not just open ones."),
1787
+ mine: z.boolean().optional().describe("Only change requests CREATED by the acting user — not ones awaiting their review.")
1793
1788
  };
1794
1789
  const listChangeRequestsPageInputSchema = z.object({
1795
1790
  ...changeRequestPageInputShape,
1796
- affectsNodeId: z.string().min(1).optional()
1791
+ affectsNodeId: z.string().min(1).optional().describe("Only change requests whose target, or any of their operations, touches this node — including Base-backed nodes.")
1797
1792
  }).optional().default({
1798
1793
  page: 1,
1799
1794
  pageSize: 50
@@ -1825,9 +1820,9 @@ const SEARCH_SOURCES = [
1825
1820
  "nodes"
1826
1821
  ];
1827
1822
  const searchInputSchema = z.object({
1828
- query: z.string().default(""),
1829
- limit: z.coerce.number().int().min(1).max(100).optional().default(20),
1830
- offset: z.coerce.number().int().min(0).optional().default(0),
1823
+ query: z.string().default("").describe("Full-text query. An empty string matches nothing."),
1824
+ limit: z.coerce.number().int().min(1).max(100).optional().default(20).describe("Results per page. Capped at 100; note the default is 20, not 50."),
1825
+ offset: z.coerce.number().int().min(0).optional().default(0).describe("0-indexed skip count. This endpoint pages by offset, not by cursor."),
1831
1826
  /**
1832
1827
  * Restrict which content this call searches. Omitted means all three
1833
1828
  * (unchanged behavior for every caller before this parameter existed).
@@ -1837,7 +1832,7 @@ const searchInputSchema = z.object({
1837
1832
  * (`?sources=records&sources=files`) becomes an array. Accept both shapes
1838
1833
  * and normalize to an array.
1839
1834
  */
1840
- sources: z.union([z.array(z.enum(SEARCH_SOURCES)), z.enum(SEARCH_SOURCES)]).transform((value) => Array.isArray(value) ? value : [value]).optional()
1835
+ sources: z.union([z.array(z.enum(SEARCH_SOURCES)), z.enum(SEARCH_SOURCES)]).transform((value) => Array.isArray(value) ? value : [value]).optional().describe("Restrict which content is searched. Omitting it searches ALL sources. Repeat the parameter to pass several (`?sources=records&sources=files`); a single occurrence is accepted as a bare value.")
1841
1836
  });
1842
1837
  const authSpaceSchema = z.object({
1843
1838
  id: z.string(),
@@ -2003,11 +1998,9 @@ const fileTreeNodeTypeSchema = z.enum([
2003
1998
  "drive",
2004
1999
  "airapp"
2005
2000
  ]);
2006
- /** `nodeId` accepts a node id or a slug. A slug is only unique *within* a type,
2007
- * so pass `type` alongside it to disambiguate; a node id needs no hint. */
2008
2001
  const fileTreeRefSchema = z.object({
2009
- nodeId: z.string(),
2010
- type: fileTreeNodeTypeSchema.optional()
2002
+ nodeId: z.string().describe("A node id OR a slug. A slug is only unique WITHIN a type, so pass `type` alongside one; a node id needs no hint."),
2003
+ type: fileTreeNodeTypeSchema.optional().describe("Disambiguates a slug. Unnecessary — and ignored — when `nodeId` is an id.")
2011
2004
  });
2012
2005
  const fileTreeContract = {
2013
2006
  create: oc.route({
@@ -2351,8 +2344,8 @@ z.object({
2351
2344
  /** `GET /assets/{assetId}/text/lines?startLine&endLine` — range capped at 2000 lines. */
2352
2345
  const ReadTextLinesInputSchema = z.object({
2353
2346
  assetId: z.string(),
2354
- startLine: z.coerce.number().int().min(1),
2355
- endLine: z.coerce.number().int().min(1)
2347
+ startLine: z.coerce.number().int().min(1).describe("First line to read. 1-indexed, and INCLUSIVE."),
2348
+ endLine: z.coerce.number().int().min(1).describe("Last line to read, INCLUSIVE. A range wider than 2000 lines is silently narrowed to the first 2000 rather than rejected — the response reports `lineCountCapped` when that happened, so check it before concluding the file ends there.")
2356
2349
  });
2357
2350
  const ReadLinesVOSchema = z.object({
2358
2351
  lines: z.array(z.string()),
@@ -2639,21 +2632,68 @@ const listRecordsSortSchema = z.object({
2639
2632
  fieldType: z.string().optional(),
2640
2633
  direction: z.enum(["asc", "desc"]).optional().default("asc")
2641
2634
  });
2635
+ const listRecordsValueFilterSchema = z.object({
2636
+ fieldSlug: z.string(),
2637
+ operator: z.enum([
2638
+ "eq",
2639
+ "ne",
2640
+ "gt",
2641
+ "gte",
2642
+ "lt",
2643
+ "lte"
2644
+ ]),
2645
+ /**
2646
+ * A number for number fields, an ISO 8601 string for date fields, a boolean
2647
+ * for checkbox, and a string for the text-like families (where only `eq`/`ne`
2648
+ * are exact — see `buildExactValueFilter`).
2649
+ */
2650
+ value: z.union([
2651
+ z.number(),
2652
+ z.string(),
2653
+ z.boolean()
2654
+ ])
2655
+ });
2656
+ /**
2657
+ * One conjunct of `valueFilters`: either a single comparison, or a disjunction
2658
+ * of them.
2659
+ *
2660
+ * `valueFilters` is an AND of these, so an entry carrying `any` makes the whole
2661
+ * list a CNF (an AND of ORs) — which is enough to express ANY boolean
2662
+ * combination of comparisons, since every formula has a CNF. That is why this
2663
+ * is one flat level rather than an arbitrarily nested tree: a recursive schema
2664
+ * would buy no expressive power, and would cost a `z.lazy` that OpenAPI
2665
+ * generation and generated clients both handle worse than a plain union.
2666
+ *
2667
+ * NOT is deliberately absent, and is not a gap: every operator here has an
2668
+ * exact negation (`eq`↔`ne`, `gt`↔`lte`, `gte`↔`lt`), so a caller pushes
2669
+ * negation down to the leaves with De Morgan before sending. Keeping NOT out of
2670
+ * the wire format keeps the server's evaluation MONOTONE, which is what makes
2671
+ * the missing-value semantics below sound.
2672
+ *
2673
+ * Missing values: a comparison against a field the record has no row for is
2674
+ * false, never true — the `EXISTS` in `buildExactValueFilter` sees no row. That
2675
+ * collapses SQL's UNKNOWN to false at each leaf, which is safe here precisely
2676
+ * because the formula is monotone: for an AND/OR tree with no NOT, "Kleene
2677
+ * three-valued result is TRUE" and "result with UNKNOWN replaced by FALSE is
2678
+ * TRUE" agree (OR is `some operand true` and AND is `all operands true` under
2679
+ * both readings). A NOT anywhere in the tree would break that equivalence,
2680
+ * which is the other reason it is not on the wire.
2681
+ */
2682
+ const listRecordsValueFilterNodeSchema = z.union([listRecordsValueFilterSchema, z.object({ any: z.array(listRecordsValueFilterSchema).min(1) })]);
2642
2683
  const listRecordsInputSchema = z.object({
2643
- limit: z.coerce.number().int().min(1).max(100).optional().default(50),
2644
- baseId: z.string().optional(),
2645
- /** Opaque base64 cursor for keyset pagination (createdAt-keyed, or sort-keyed when `sort` is set). */
2646
- cursor: z.string().optional(),
2684
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50).describe("Records per page. Capped at 100; ask for the next page with `cursor`."),
2685
+ baseId: z.string().optional().describe("Restrict to one Base. OMITTING it lists records across the WHOLE SPACE, which is rarely what a caller means and is easy to miss — every other parameter still applies, so an unscoped query looks like it worked."),
2686
+ cursor: z.string().optional().describe("Opaque page cursor: pass back the `nextCursor` from the previous response. Do not construct or parse it — it is keyed on `createdAt`, or on the sort field when `sort` is given, and that is an implementation detail."),
2687
+ status: z.enum(["active", "archived"]).optional().default("active").describe("`active` is the live table; `archived` is the Base's trash."),
2688
+ filters: z.array(listRecordsFilterSchema).optional().describe("View filters — a best-effort SUPERSET, not an exact answer. They mean what the grid means (a currency number reads as \"$1,234.00\", a select as its choice label), and the server may return records that do not match, so the caller must narrow them again and must NOT trust `limit` alongside them. Use `valueFilters` when you need an exact row set."),
2647
2689
  /**
2648
- * `active` (default) is the live table; `archived` is the Base's trash — the
2649
- * same keyset pagination either way, which is why these are one endpoint
2650
- * rather than a `/records/archived` twin.
2690
+ * Exact value comparisons. Unlike `filters` these are authoritative — the
2691
+ * returned rows are exactly those that match, so a caller can page and
2692
+ * limit against them. ANDed together (and with `filters` when both are
2693
+ * given); an entry may be a disjunction, making the list a CNF.
2651
2694
  */
2652
- status: z.enum(["active", "archived"]).optional().default("active"),
2653
- /** View filters for server-side push-down (superset; client still narrows). */
2654
- filters: z.array(listRecordsFilterSchema).optional(),
2655
- /** View sort for server-side push-down (number/date fields only). */
2656
- sort: listRecordsSortSchema.optional()
2695
+ valueFilters: z.array(listRecordsValueFilterNodeSchema).optional().describe("EXACT value comparisons, unlike `filters` which are a best-effort superset. The returned rows are exactly those that match, so `limit` can be trusted alongside them. Entries are ANDed; an entry may instead be `{ any: [...] }` to OR its comparisons, which makes the list a CNF and can express any boolean combination. Requires `baseId`. Compares number, date, checkbox and text-like fields (text and select support eq/ne only); anything else is a 400 rather than a silently dropped condition."),
2696
+ sort: listRecordsSortSchema.optional().describe("Sort by one field. Only number and date fields sort authoritatively (their typed value column matches a client's own ordering); any other field type is returned in the default order and left for the caller to sort.")
2657
2697
  }).optional().default({
2658
2698
  limit: 50,
2659
2699
  status: "active"
@@ -2663,8 +2703,8 @@ const listRecordsResponseSchema = z.object({
2663
2703
  nextCursor: z.string().nullable()
2664
2704
  });
2665
2705
  const listRecordsPageInputSchema = z.object({
2666
- baseId: z.string().min(1),
2667
- viewId: z.string().min(1).optional(),
2706
+ baseId: z.string().min(1).describe("Required here, unlike `records.list` where omitting it spans the whole space."),
2707
+ viewId: z.string().min(1).optional().describe("Show only what this saved View would: its filters and its sort."),
2668
2708
  /**
2669
2709
  * Extra conditions ANDed with the View's own filters — "this View, further
2670
2710
  * narrowed". The motivating case is one board column: the saved View's
@@ -2677,7 +2717,7 @@ const listRecordsPageInputSchema = z.object({
2677
2717
  * That distinction is the whole point — a *superset* page can be missing
2678
2718
  * records, and a board column that silently drops cards reads as data loss.
2679
2719
  */
2680
- filters: z.array(listRecordsFilterSchema).optional(),
2720
+ filters: z.array(listRecordsFilterSchema).optional().describe("Extra conditions ANDed with the View's own — \"this View, further narrowed\". Unlike `records.list`'s `filters`, these are EXACT: every page is precisely what the client's own matcher would keep, so a page is never missing records it should hold."),
2681
2721
  /**
2682
2722
  * Scope the page to records whose `date`/`created_time`/`updated_time` field
2683
2723
  * falls in `[gte, lt)` — an absolute UTC instant range, not a `filters`
@@ -2695,13 +2735,11 @@ const listRecordsPageInputSchema = z.object({
2695
2735
  */
2696
2736
  dateRange: z.object({
2697
2737
  fieldSlug: z.string().min(1),
2698
- /** Inclusive lower bound, ISO 8601 UTC instant. */
2699
- gte: z.string(),
2700
- /** Exclusive upper bound, ISO 8601 UTC instant. */
2701
- lt: z.string()
2702
- }).optional(),
2703
- page: z.coerce.number().int().min(1).optional().default(1),
2704
- pageSize: z.coerce.number().int().min(1).max(100).optional().default(50)
2738
+ gte: z.string().describe("Inclusive lower bound, ISO 8601 UTC instant."),
2739
+ lt: z.string().describe("EXCLUSIVE upper bound, ISO 8601 UTC instant.")
2740
+ }).optional().describe("Scope the page to a `date`/`created_time`/`updated_time` field falling in `[gte, lt)` a half-open range of absolute UTC instants, not a `filters` condition. Resolve the bounds yourself: a day or a month only means something in a timezone, and the server does not know the viewer's."),
2741
+ page: z.coerce.number().int().min(1).optional().default(1).describe("1-indexed, not 0-indexed."),
2742
+ pageSize: z.coerce.number().int().min(1).max(100).optional().default(50).describe("Records per page. Capped at 100.")
2705
2743
  });
2706
2744
  const listRecordsPageResponseSchema = z.object({
2707
2745
  records: z.array(recordSchema),
@@ -2711,13 +2749,8 @@ const listRecordsPageResponseSchema = z.object({
2711
2749
  pageSize: z.number().int().min(1).max(100)
2712
2750
  });
2713
2751
  const countRecordsInputSchema = z.object({
2714
- baseId: z.string().optional(),
2715
- /**
2716
- * Count only the rows a saved View would display (the View's filters
2717
- * applied; its sort is ignored — a count doesn't need an order). A View
2718
- * belongs to exactly one Base, so this requires `baseId`.
2719
- */
2720
- viewId: z.string().optional(),
2752
+ baseId: z.string().optional().describe("Restrict to one Base. Omitting it counts every record in the space. Required as soon as `viewId`, `filters` or `valueFilters` is given — a field slug only means something within one Base."),
2753
+ viewId: z.string().optional().describe("Count only what this saved View would display. Its filters apply; its sort is ignored, since a count has no order. Requires `baseId`."),
2721
2754
  /**
2722
2755
  * Ad-hoc filter conditions — same shape `records.list`'s `filters` uses —
2723
2756
  * for composing a condition set without a saved View (e.g. an AirApp
@@ -2728,8 +2761,23 @@ const countRecordsInputSchema = z.object({
2728
2761
  * never the caller-supplied `fieldType` hint, which elsewhere is only a
2729
2762
  * pushdown hint and isn't trustworthy enough for an exact count.
2730
2763
  */
2731
- filters: z.array(listRecordsFilterSchema).optional()
2764
+ filters: z.array(listRecordsFilterSchema).optional().describe("Ad-hoc conditions, ANDed with the View's own when `viewId` is also given. Unlike `records.list`'s superset `filters`, the COUNT is exact either way — but a condition whose exactness cannot be proven makes the server read every candidate row instead of running one aggregate, so prefer `valueFilters` where it fits. Requires `baseId`."),
2765
+ /**
2766
+ * Exact value comparisons, same shape and meaning as `records.list`'s.
2767
+ *
2768
+ * Worth having here specifically because they are ALWAYS exact: a count
2769
+ * scoped only by these stays a single SQL `count(*)`, where an ad-hoc view
2770
+ * `filters` set that cannot be proven exact falls back to reading every
2771
+ * candidate row and counting the survivors. Requires `baseId` for the same
2772
+ * reason `filters` does — a field slug is only unambiguous within one Base.
2773
+ */
2774
+ valueFilters: z.array(listRecordsValueFilterNodeSchema).optional().describe("EXACT value comparisons, same shape as `records.list`'s. Always exact, so a count scoped only by these stays a single SQL count instead of reading every candidate row. Requires `baseId`.")
2732
2775
  }).superRefine((value, ctx) => {
2776
+ if (value.valueFilters?.length && !value.baseId) ctx.addIssue({
2777
+ code: "custom",
2778
+ path: ["baseId"],
2779
+ message: "baseId is required when valueFilters is given"
2780
+ });
2733
2781
  if (value.viewId && !value.baseId) ctx.addIssue({
2734
2782
  code: "custom",
2735
2783
  path: ["baseId"],
@@ -2744,9 +2792,30 @@ const countRecordsInputSchema = z.object({
2744
2792
  const countRecordsResponseSchema = z.object({
2745
2793
  /** Total active records in the space (optionally scoped to a base). */
2746
2794
  total: z.number().int().nonnegative() });
2795
+ /**
2796
+ * A numeric aggregate over the records in a group.
2797
+ *
2798
+ * Exact for the same reason `valueFilters` are: `value_number` holds the value
2799
+ * itself, so `sum`/`avg`/`min`/`max` are ordinary SQL over a real column with
2800
+ * no formatting layer to diverge from. Restricted to number-shaped fields for
2801
+ * exactly that reason — there is no column to add up on the others.
2802
+ *
2803
+ * `count` here counts rows whose value is PRESENT, which is not the same as the
2804
+ * group's `count` (that one counts records, present value or not). Both are
2805
+ * useful and SQL distinguishes them, so both are available.
2806
+ */
2807
+ const recordAggregateSchema = z.object({
2808
+ fn: z.enum([
2809
+ "sum",
2810
+ "avg",
2811
+ "min",
2812
+ "max",
2813
+ "count"
2814
+ ]),
2815
+ fieldSlug: z.string().min(1)
2816
+ });
2747
2817
  const groupRecordsInputSchema = z.object({
2748
- /** Group within exactly one Base — a field slug is only unambiguous there. */
2749
- baseId: z.string().min(1),
2818
+ baseId: z.string().min(1).describe("Required: a field slug is only unambiguous within one Base."),
2750
2819
  /**
2751
2820
  * The field to group by. Restricted to `select` and `checkbox`: their stored
2752
2821
  * value IS the grouping key (a choice id / a boolean), so a SQL GROUP BY
@@ -2754,28 +2823,74 @@ const groupRecordsInputSchema = z.object({
2754
2823
  * be truncated at the projection limit, and date keys would bucket by the
2755
2824
  * server's timezone rather than the viewer's — both would report a
2756
2825
  * confidently wrong split, so they're rejected instead of approximated.
2826
+ *
2827
+ * OMIT it to aggregate the whole filtered set as a single bucket. That is the
2828
+ * shape a summary tile wants ("total pipeline value"), and it costs one query
2829
+ * instead of reading every record to add them up client-side.
2757
2830
  */
2758
- fieldSlug: z.string().min(1),
2759
- /** Group only the rows a saved View would display (its filters; sort ignored). */
2760
- viewId: z.string().min(1).optional(),
2761
- /** Ad-hoc filters, ANDed with the View's own when both are given. */
2762
- filters: z.array(listRecordsFilterSchema).optional()
2831
+ fieldSlug: z.string().min(1).optional().describe("The field to group by. OMIT it to aggregate the whole filtered set as a single bucket, which is what a summary tile wants. Under the default `grid` bucketing only `select` and `checkbox` can be grouped; `sql` bucketing also allows number and date fields."),
2832
+ /**
2833
+ * Which bucketing rules to use, and they genuinely differ.
2834
+ *
2835
+ * `grid` (the default, and what this endpoint has always done) buckets the
2836
+ * way the GRID renders: an unset checkbox folds in with `false`, and an empty
2837
+ * string folds into the null bucket. That is right for a Kanban column header
2838
+ * — a card with no value belongs under "false", not in a fourth column.
2839
+ *
2840
+ * `sql` buckets the way `GROUP BY` does: a missing value gets its OWN bucket
2841
+ * and nothing is folded. That is right for a caller reproducing SQL — an ORM
2842
+ * driver, or anything comparing this against a database — and it is what lets
2843
+ * such a caller trust the server's answer instead of re-grouping locally.
2844
+ *
2845
+ * The two disagree on real data, which is why this is a choice rather than a
2846
+ * fix: neither is a better version of the other.
2847
+ */
2848
+ bucketing: z.enum(["grid", "sql"]).optional().default("grid").describe("How records are bucketed, and the two modes disagree on real data. `grid` (default) buckets the way the grid renders: an unset checkbox counts as `false` and an empty string falls in the null bucket — right for a Kanban column header. `sql` buckets the way GROUP BY does: a missing value gets its OWN bucket and nothing is folded — right for anything reproducing SQL. `sql` also returns keys in their own type (a number for a number field) rather than as strings."),
2849
+ /**
2850
+ * Numeric aggregates evaluated per group, keyed in the response as
2851
+ * `"<fn>:<fieldSlug>"`. Without this the response is counts only, exactly as
2852
+ * before.
2853
+ */
2854
+ aggregates: z.array(recordAggregateSchema).optional().describe("Numeric aggregates evaluated per group, keyed in the response as `\"<fn>:<fieldSlug>\"`. Only number-shaped fields can be aggregated; anything else is a 400. `sum`/`avg`/`min`/`max` of a group holding no values are NULL rather than 0, and `count` over a FIELD counts present values — which is not the same as the group's own `count`, which counts records."),
2855
+ viewId: z.string().min(1).optional().describe("Group only what this saved View would display. Its filters apply; its sort is ignored."),
2856
+ filters: z.array(listRecordsFilterSchema).optional().describe("Ad-hoc conditions, ANDed with the View's own when both are given. The grouping is exact either way, but a condition whose exactness cannot be proven makes the server read every candidate row instead of running one GROUP BY."),
2857
+ /**
2858
+ * Exact value comparisons, ANDed with everything above. Always exact, so a
2859
+ * grouping scoped only by these stays a single SQL GROUP BY.
2860
+ */
2861
+ valueFilters: z.array(listRecordsValueFilterNodeSchema).optional().describe("EXACT value comparisons, same shape as `records.list`'s. Always exact, so a grouping scoped only by these stays a single SQL GROUP BY.")
2763
2862
  });
2764
2863
  const groupRecordsResponseSchema = z.object({
2765
2864
  groups: z.array(z.object({
2766
2865
  /**
2767
- * The raw stored key: a `select` choice id, or `"true"`/`"false"` for a
2768
- * checkbox. For a select, `null` is the bucket of records with no value
2769
- * (what a Kanban board shows as its "Uncategorized" column). A checkbox
2770
- * never reports `null` an unset checkbox counts as `"false"`, matching
2771
- * how view filters already treat it (`is_false` covers null/undefined).
2866
+ * The raw stored key.
2867
+ *
2868
+ * Under `grid` bucketing (the default) this is always a string or null: a
2869
+ * `select` choice id, or `"true"`/`"false"` for a checkbox, where `null`
2870
+ * is the bucket of records with no value (a Kanban board's
2871
+ * "Uncategorized" column) and an unset checkbox counts as `"false"`.
2872
+ *
2873
+ * Under `sql` bucketing it is the STORED value in its own type — a number
2874
+ * for a number field, a boolean for a checkbox, an ISO string for a date
2875
+ * — and `null` means the record has no value for that field, which under
2876
+ * these rules is a bucket of its own rather than folded into another.
2772
2877
  *
2773
2878
  * Choice LABELS are deliberately not resolved here — the client already
2774
2879
  * holds the Base's field definitions and renders labels itself, and
2775
2880
  * returning ids keeps this response stable across a choice rename.
2776
2881
  */
2777
- value: z.string().nullable(),
2778
- count: z.number().int().nonnegative()
2882
+ value: z.union([
2883
+ z.string(),
2884
+ z.number(),
2885
+ z.boolean()
2886
+ ]).nullable().describe("The bucket key. Under `grid` bucketing always a string or null (a select's choice, or `\"true\"`/`\"false\"` for a checkbox, with null meaning \"no value\"). Under `sql` bucketing it is the stored value in its own type, and null is the bucket of records that have no value for the field."),
2887
+ count: z.number().int().nonnegative().describe("Records in this bucket."),
2888
+ /**
2889
+ * Present only when `aggregates` was requested. Keyed `"<fn>:<fieldSlug>"`.
2890
+ * A value of `null` means the group held no rows with that field set —
2891
+ * which is NOT the same as `0`, and a dashboard renders them differently.
2892
+ */
2893
+ aggregates: z.record(z.string(), z.number().nullable()).optional().describe("Present only when `aggregates` was requested, keyed `\"<fn>:<fieldSlug>\"`. A null value means the bucket held no records with that field set — not zero.")
2779
2894
  })),
2780
2895
  /** Sum of every group's count — the same number `records.count` would return. */
2781
2896
  total: z.number().int().nonnegative()
@@ -2822,10 +2937,10 @@ const createBulkUpdateChangeRequestInputSchema = z.object({
2822
2937
  }
2823
2938
  });
2824
2939
  const recordFieldFilterInputSchema = z.object({
2825
- baseId: z.string().optional(),
2826
- fieldSlug: z.string().min(1),
2827
- valueText: z.string().min(1),
2828
- limit: z.coerce.number().int().min(1).max(100).optional().default(50)
2940
+ baseId: z.string().optional().describe("Restrict to one Base. Omitting it searches the whole space."),
2941
+ fieldSlug: z.string().min(1).describe("The field's SLUG, not its display name — visible in the Base's field settings."),
2942
+ valueText: z.string().min(1).describe("Matched by EXACT equality, not substring or fuzzy — this is the de-dup-by-key lookup. Use `/api/v1/search` for full-text."),
2943
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50).describe("Maximum matches to return. Capped at 100.")
2829
2944
  });
2830
2945
  const recordFieldGetInputSchema = z.object({
2831
2946
  baseId: z.string().describe("Field selector: Base id. Requires fieldSlug and valueText."),
@@ -3099,8 +3214,8 @@ const docContract = { create: oc.route({
3099
3214
  */
3100
3215
  const ReadNodeLinesInputSchema = z.object({
3101
3216
  nodeId: z.string(),
3102
- startLine: z.coerce.number().int().min(1),
3103
- endLine: z.coerce.number().int().min(1)
3217
+ startLine: z.coerce.number().int().min(1).describe("First line to read. 1-indexed, and INCLUSIVE."),
3218
+ endLine: z.coerce.number().int().min(1).describe("Last line to read, INCLUSIVE. A range wider than 2000 lines is silently narrowed to the first 2000 rather than rejected — the response reports `lineCountCapped` when that happened, so check it before concluding the file ends there.")
3104
3219
  });
3105
3220
  //#endregion
3106
3221
  //#region ../../packages/busabase-contract/src/domains/dump/types.ts
@@ -3418,10 +3533,9 @@ const FormVOSchema = z.object({
3418
3533
  updatedAt: z.string()
3419
3534
  });
3420
3535
  const ListFormsInputSchema = z.object({
3421
- targetBaseId: z.string().min(1),
3422
- limit: z.coerce.number().int().min(1).max(100).optional().default(50),
3423
- /** Opaque createdAt/id keyset cursor. */
3424
- cursor: z.string().optional()
3536
+ targetBaseId: z.string().min(1).describe("The Base the forms WRITE INTO — required; this is not a space-wide listing."),
3537
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50).describe("Forms per page. Capped at 100; ask for the next page with `cursor`."),
3538
+ cursor: z.string().optional().describe("Opaque page cursor: pass back the `nextCursor` from the previous response. Do not construct or parse it.")
3425
3539
  });
3426
3540
  const ListFormsVOSchema = z.object({
3427
3541
  forms: z.array(FormVOSchema),
@@ -3754,9 +3868,11 @@ const installContract = {
3754
3868
  * Template Center catalog types (pure zod, client-safe).
3755
3869
  *
3756
3870
  * The catalog is the file `busabase-cli index` builds from a skills repository
3757
- * — see `busabase-package/index-build`. It is re-declared here rather than
3758
- * imported because that module is Node-only (it reads packages), and these
3759
- * shapes are rendered in a browser.
3871
+ * — see `busabase-package/index-build`. Most shapes are re-declared here rather
3872
+ * than imported because that module is Node-only (it reads packages) and these
3873
+ * are rendered in a browser; `TemplateRiskLevelSchema` is the one exception,
3874
+ * imported from the package domain because it is already pure zod and a third
3875
+ * copy of the same enum is a worse outcome than the cross-domain import.
3760
3876
  *
3761
3877
  * Spec: `apps/busabase/content/spec/template-center.md` §6.4.
3762
3878
  */
@@ -3775,6 +3891,8 @@ const TemplateCardVOSchema = z.object({
3775
3891
  name: z.string(),
3776
3892
  description: z.string(),
3777
3893
  category: z.string(),
3894
+ /** Absent when undeclared or unrecognized — the card shows "undeclared", never a guess. */
3895
+ risk: TemplateRiskLevelSchema.optional(),
3778
3896
  tags: z.array(z.string()).default([]),
3779
3897
  /**
3780
3898
  * Absolute URLs, resolved server-side.
@@ -3812,9 +3930,7 @@ const TemplateCatalogVOSchema = z.object({
3812
3930
  */
3813
3931
  error: z.string().optional()
3814
3932
  });
3815
- const ListTemplatesDTOSchema = z.object({
3816
- /** Bypass the cache — the refresh button. */
3817
- refresh: z.boolean().optional() }).optional().default({});
3933
+ const ListTemplatesDTOSchema = z.object({ refresh: z.boolean().optional().describe("Bypass the cache and re-fetch the catalogue — what the refresh button does. Slower; leave it off for ordinary reads.") }).optional().default({});
3818
3934
  //#endregion
3819
3935
  //#region ../../packages/busabase-contract/src/domains/templates/contract.ts
3820
3936
  /**
@@ -3919,6 +4035,7 @@ const vaultContract = {
3919
4035
  //#region ../../packages/busabase-contract/src/domains/webhook/types.ts
3920
4036
  const WebhookEventTypeSchema = z.enum([
3921
4037
  "record.created",
4038
+ "record.updated",
3922
4039
  "ai_mention",
3923
4040
  "changes_requested",
3924
4041
  "asset.uploaded"
@@ -4034,7 +4151,7 @@ const WebhookDeliveryVOSchema = z.object({
4034
4151
  z.object({}).optional().default({});
4035
4152
  const ListWebhookDeliveriesInputSchema = z.object({
4036
4153
  ruleId: z.string(),
4037
- limit: z.coerce.number().int().min(1).max(100).default(20)
4154
+ limit: z.coerce.number().int().min(1).max(100).default(20).describe("Delivery attempts to return, newest first.")
4038
4155
  });
4039
4156
  //#endregion
4040
4157
  //#region ../../packages/busabase-contract/src/domains/webhook/contract.ts
@@ -4124,10 +4241,9 @@ const activityItemSchema = z.discriminatedUnion("kind", [
4124
4241
  record: recordSchema.nullable()
4125
4242
  })
4126
4243
  ]);
4127
- /** Keyset page request: opaque base64 cursor (`ts|kind|id`) + page size. */
4128
4244
  const listActivityPagedInputSchema = z.object({
4129
- limit: z.coerce.number().int().min(1).max(100).optional().default(50),
4130
- cursor: z.string().optional()
4245
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50).describe("Items per page. Capped at 100; ask for the next page with `cursor`."),
4246
+ cursor: z.string().optional().describe("Opaque page cursor: pass back the `nextCursor` from the previous response. Do not construct or parse it.")
4131
4247
  }).optional().default({ limit: 50 });
4132
4248
  const listActivityResponseSchema = z.object({
4133
4249
  items: z.array(activityItemSchema),
@@ -4135,13 +4251,13 @@ const listActivityResponseSchema = z.object({
4135
4251
  });
4136
4252
  /** Node-scoped raw activity stream request — offset/limit only, no cursor. */
4137
4253
  const listNodeActivityInputSchema = z.object({
4138
- nodeId: z.string().min(1),
4139
- limit: z.coerce.number().int().min(1).max(100).optional().default(50)
4254
+ nodeId: z.string().min(1).describe("Activity is scoped to this node alone, not its subtree."),
4255
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50).describe("Most recent events to return, capped at 100. This stream has NO cursor — it is a recent window, not a pageable history; use `/api/v1/activity/paged` to walk further back.")
4140
4256
  });
4141
4257
  /** Record-scoped raw activity stream request — same shape as node, keyed on recordId. */
4142
4258
  const listRecordActivityInputSchema = z.object({
4143
- recordId: z.string().min(1),
4144
- limit: z.coerce.number().int().min(1).max(100).optional().default(50)
4259
+ recordId: z.string().min(1).describe("Activity for this record's own history."),
4260
+ limit: z.coerce.number().int().min(1).max(100).optional().default(50).describe("Most recent events to return, capped at 100. This stream has NO cursor — it is a recent window, not a pageable history; use `/api/v1/activity/paged` to walk further back.")
4145
4261
  });
4146
4262
  const EMBED_LINK_MAX_MINUTES = 1440;
4147
4263
  const EmbedNodeTypeSchema = z.enum([
@@ -4220,8 +4336,8 @@ const CreateEmbedLinkInputSchema = z.object({
4220
4336
  })
4221
4337
  });
4222
4338
  const ListEmbedLinksInputSchema = z.object({
4223
- type: EmbedTargetTypeSchema.optional(),
4224
- typeId: z.string().min(1).optional()
4339
+ type: EmbedTargetTypeSchema.optional().describe("Which kind of target the links point at. Omitting it returns links of every kind."),
4340
+ typeId: z.string().min(1).optional().describe("The target's id, interpreted according to `type`.")
4225
4341
  }).optional().default({});
4226
4342
  const RevokeEmbedLinkInputSchema = z.object({ id: z.string().min(1) });
4227
4343
  const EmbedLinkVOSchema = z.object({
@@ -0,0 +1,147 @@
1
+ import { z } from "zod";
2
+ //#region ../../packages/busabase-contract/src/domains/skill/frontmatter.ts
3
+ /**
4
+ * `SKILL.md` frontmatter — the generic shape, independent of Busabase.
5
+ *
6
+ * This used to live inside `domains/package/template.ts`, which encoded an
7
+ * assumption that no longer holds: that a Skill is a *part of* a template. Most
8
+ * Skills are not. A Skill is a directory with a `SKILL.md` an agent reads; it
9
+ * needs no `busabase.json`, ships no `content/`, and never installs into a
10
+ * workspace. Busabase's own `busabase-app-creator` is one.
11
+ *
12
+ * Modelling it only as a template component meant there was nowhere to hang
13
+ * checks for a plain Skill, so there were none. Hence its own file, in the
14
+ * domain it belongs to.
15
+ *
16
+ * `metadata` is deliberately open: a Skill carries whatever its ecosystem
17
+ * defines (categories, tags, risk labels, per-agent hints). The Busabase-specific
18
+ * block that decides template-ness is layered on in `domains/package/template.ts`
19
+ * — the template format knows about Skills, not the other way round.
20
+ */
21
+ const SkillFrontmatterSchema$1 = z.object({
22
+ /** Identity. For a Skill inside a package this must equal the package name. */
23
+ name: z.string().min(1),
24
+ /**
25
+ * How an agent decides whether to reach for this Skill at all — so an empty one
26
+ * is not a cosmetic omission, it is a Skill that never gets picked.
27
+ */
28
+ description: z.string().default(""),
29
+ metadata: z.object({}).passthrough().optional()
30
+ });
31
+ const TemplateAirAppRefSchema = z.object({
32
+ /** Slug of the `content/<dir>` holding the AirApp. */
33
+ slug: z.string().min(1),
34
+ role: z.enum([
35
+ "primary",
36
+ "admin",
37
+ "public",
38
+ "tool"
39
+ ]),
40
+ label: z.string().optional()
41
+ });
42
+ /**
43
+ * Secrets the app expects to find in the Vault.
44
+ *
45
+ * DECLARED, never created: the package format has no slot for secret values and
46
+ * must not grow one (the same "you cannot leak what the format cannot express"
47
+ * rule the whole format is built on). Install surfaces these as a post-install
48
+ * prompt; the user fills them in the Vault themselves.
49
+ */
50
+ const TemplateSecretSchema = z.object({
51
+ key: z.string().min(1),
52
+ description: z.string().default(""),
53
+ required: z.boolean().default(true)
54
+ });
55
+ z.object({
56
+ /** Template Center category, e.g. `"crm"`, `"email"`, `"content"`. */
57
+ category: z.string().min(1),
58
+ tags: z.array(z.string()).default([]),
59
+ /** Card/detail screenshots, package-relative (`assets/screenshots/overview.webp`). */
60
+ screenshots: z.array(z.string()).default([]),
61
+ /**
62
+ * Ready-made prompts shown after install ("Ask agent" prefills the first).
63
+ *
64
+ * They are the difference between a folder of tables and something a user can
65
+ * *use*: the point of a template is that the agent already knows the job, and
66
+ * these are how that is made visible rather than left for the user to guess.
67
+ */
68
+ agentPrompts: z.array(z.string()).default([]),
69
+ /** Single-AirApp shorthand. Mutually exclusive with `airapps`. */
70
+ airapp: z.string().optional(),
71
+ /** Multi-AirApp form. Exactly one entry must have `role: "primary"`. */
72
+ airapps: z.array(TemplateAirAppRefSchema).optional(),
73
+ /**
74
+ * Bumped by the author when the declared resource shape changes.
75
+ *
76
+ * Part of the ownership stamp, so BOTH doors must agree on it: the installer
77
+ * writes it, and a skill's own `setup.mjs` compares against it to decide
78
+ * whether a node it finds is its own current shape or an older one to repair.
79
+ * Defaulted rather than required so an author who never versions their app
80
+ * still gets a stamp both sides recognise.
81
+ */
82
+ schemaVersion: z.number().int().nonnegative().default(1),
83
+ vaultNamespace: z.string().optional(),
84
+ secrets: z.array(TemplateSecretSchema).default([]),
85
+ requires: z.object({ airapp: z.boolean().optional() }).default({})
86
+ });
87
+ const TemplateRiskLevelSchema = z.enum([
88
+ "gated-write",
89
+ "local-write",
90
+ "read-only",
91
+ "sandbox"
92
+ ]);
93
+ /**
94
+ * `metadata.busabase` inside the root `SKILL.md`'s YAML frontmatter.
95
+ *
96
+ * `template: true` is an EXPLICIT opt-in, not an inference from "this skill
97
+ * happens to contain a package". Publishing a template means accepting that
98
+ * installers will run its AirApp code and feed its SKILL.md to their agent; that
99
+ * deserves a deliberate flag rather than a side effect of directory shape.
100
+ */
101
+ const SkillBusabaseMetadataSchema = z.object({
102
+ template: z.boolean().default(false),
103
+ folderSlug: z.string().optional(),
104
+ /** Resource keys the manual talks about; each must exist under `content/`. */
105
+ resources: z.array(z.string()).default([]),
106
+ /**
107
+ * Free-form on purpose — see `parseTemplateRisk`. Validating this to the enum
108
+ * here would turn a stranger's typo or a retired term into a hard parse
109
+ * failure for the whole frontmatter, which is a worse outcome than a card
110
+ * that cannot show a risk badge.
111
+ */
112
+ risk: z.string().optional()
113
+ });
114
+ SkillFrontmatterSchema$1.extend({ metadata: z.object({ busabase: SkillBusabaseMetadataSchema.optional() }).passthrough().optional() });
115
+ /** Stamp on every resource node (Base, Drive, AirApp, …) an app owns. */
116
+ const AppResourceOwnershipSchema = z.object({
117
+ appId: z.string().min(1),
118
+ /** Stable internal handle (`"contacts"`), NOT the installed slug. */
119
+ resourceKey: z.string().min(1),
120
+ schemaVersion: z.number().int().nonnegative()
121
+ });
122
+ /**
123
+ * The `resourceKey` reserved for an app's root Folder.
124
+ *
125
+ * `busabase-sdk` recognises an app's own Folder by looking for exactly this
126
+ * value (`ownsAppRoot`), so the installer must write it too — a Folder stamped
127
+ * with anything else reads as a stranger's, and the skill's own `setup.mjs`
128
+ * then refuses to touch its own workspace with `SETUP_CONFLICT`. Exported so
129
+ * neither side carries the string literal privately.
130
+ */
131
+ const APP_ROOT_RESOURCE_KEY = "app-root";
132
+ AppResourceOwnershipSchema.extend({
133
+ resourceKey: z.literal(APP_ROOT_RESOURCE_KEY),
134
+ version: z.string().optional(),
135
+ source: z.object({
136
+ repo: z.string().optional(),
137
+ ref: z.string().optional(),
138
+ subdir: z.string().optional()
139
+ }).optional(),
140
+ installedAt: z.string().optional()
141
+ });
142
+ z.object({
143
+ appId: z.string().min(1),
144
+ ["isTemplateSkill"]: z.literal(true)
145
+ });
146
+ //#endregion
147
+ export { TemplateRiskLevelSchema as n, APP_ROOT_RESOURCE_KEY as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busabase-sdk",
3
- "version": "0.42.0",
3
+ "version": "0.50.0",
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",
@@ -67,9 +67,9 @@
67
67
  "tsx": "^4.20.5",
68
68
  "typescript": "^7.0.2",
69
69
  "vitest": "^4.1.11",
70
+ "busabase-contract": "0.50.0",
70
71
  "open-domains": "0.0.2",
71
- "openlib": "0.1.1",
72
- "busabase-contract": "0.42.0"
72
+ "openlib": "0.1.1"
73
73
  },
74
74
  "engines": {
75
75
  "node": ">=24.18.0"