busabase-sdk 0.18.0 → 0.19.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 +10 -1
- package/dist/airapp.js +154 -4
- package/dist/{client-CbUceGzy.d.ts → client-slzL_ils.d.ts} +37 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +100 -0
- package/package.json +4 -4
package/dist/airapp.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as BusabaseClient } from "./client-
|
|
1
|
+
import { t as BusabaseClient } from "./client-slzL_ils.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,
|
|
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:
|
|
78
|
-
metadata: resourceMetadata(config,
|
|
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,
|
|
292
|
+
metadata: resourceMetadata(config, APP_ROOT_RESOURCE_KEY)
|
|
143
293
|
});
|
|
144
294
|
for (const base of missingBases) operations.push({
|
|
145
295
|
kind: "create",
|
|
@@ -10635,6 +10635,7 @@ declare const cloudContract: {
|
|
|
10635
10635
|
workspace: "workspace";
|
|
10636
10636
|
}>>>;
|
|
10637
10637
|
version: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
10638
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
10638
10639
|
files: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
10639
10640
|
path: z.ZodString;
|
|
10640
10641
|
assetId: z.ZodString;
|
|
@@ -13565,6 +13566,7 @@ declare const cloudContract: {
|
|
|
13565
13566
|
}>;
|
|
13566
13567
|
version: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
13567
13568
|
available: z.ZodDefault<z.ZodBoolean>;
|
|
13569
|
+
comingSoon: z.ZodDefault<z.ZodBoolean>;
|
|
13568
13570
|
unavailableReason: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
13569
13571
|
connectionRequired: z.ZodDefault<z.ZodBoolean>;
|
|
13570
13572
|
connectedAgentName: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
@@ -14336,6 +14338,41 @@ declare const cloudContract: {
|
|
|
14336
14338
|
warnings: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
14337
14339
|
}, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
|
|
14338
14340
|
};
|
|
14341
|
+
templates: {
|
|
14342
|
+
list: import("@orpc/contract").ContractProcedure<z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
|
14343
|
+
refresh: z.ZodOptional<z.ZodBoolean>;
|
|
14344
|
+
}, z.core.$strip>>>, z.ZodObject<{
|
|
14345
|
+
templates: z.ZodArray<z.ZodObject<{
|
|
14346
|
+
id: z.ZodString;
|
|
14347
|
+
name: z.ZodString;
|
|
14348
|
+
description: z.ZodString;
|
|
14349
|
+
category: z.ZodString;
|
|
14350
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
14351
|
+
screenshots: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
14352
|
+
agentPrompts: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
14353
|
+
version: z.ZodOptional<z.ZodString>;
|
|
14354
|
+
author: z.ZodOptional<z.ZodString>;
|
|
14355
|
+
license: z.ZodOptional<z.ZodString>;
|
|
14356
|
+
stats: z.ZodObject<{
|
|
14357
|
+
folders: z.ZodNumber;
|
|
14358
|
+
docs: z.ZodNumber;
|
|
14359
|
+
bases: z.ZodNumber;
|
|
14360
|
+
records: z.ZodNumber;
|
|
14361
|
+
files: z.ZodNumber;
|
|
14362
|
+
airapps: z.ZodNumber;
|
|
14363
|
+
skill: z.ZodBoolean;
|
|
14364
|
+
}, z.core.$strip>;
|
|
14365
|
+
install: z.ZodObject<{
|
|
14366
|
+
repoUrl: z.ZodString;
|
|
14367
|
+
intoFolder: z.ZodString;
|
|
14368
|
+
}, z.core.$strip>;
|
|
14369
|
+
sourceUrl: z.ZodString;
|
|
14370
|
+
}, z.core.$strip>>;
|
|
14371
|
+
repo: z.ZodString;
|
|
14372
|
+
ref: z.ZodString;
|
|
14373
|
+
error: z.ZodOptional<z.ZodString>;
|
|
14374
|
+
}, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, Record<never, never>>, Record<never, never>>;
|
|
14375
|
+
};
|
|
14339
14376
|
changeRequests: {
|
|
14340
14377
|
list: import("@orpc/contract").ContractProcedure<z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
|
14341
14378
|
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-
|
|
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-slzL_ils.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),
|
|
@@ -1616,6 +1618,17 @@ const createFileTreeInputSchema = z.object({
|
|
|
1616
1618
|
"public"
|
|
1617
1619
|
]).optional().default("private"),
|
|
1618
1620
|
version: z.string().optional().default("0.1.0"),
|
|
1621
|
+
/**
|
|
1622
|
+
* Extra node metadata, stored alongside the server-owned keys.
|
|
1623
|
+
*
|
|
1624
|
+
* Rides along the change request on the review-first path, so it lands when a
|
|
1625
|
+
* human merges. That is the whole point: an ownership stamp applied only
|
|
1626
|
+
* after an immediate create would silently never be applied to a node that
|
|
1627
|
+
* was proposed instead — leaving the app unable to recognise its own
|
|
1628
|
+
* resources on the DEFAULT install path. Server-owned keys (`visibility`,
|
|
1629
|
+
* `version`) always win, so a caller cannot use this to rewrite them.
|
|
1630
|
+
*/
|
|
1631
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
1619
1632
|
files: z.array(z.union([assetFileInputSchema, textFileInputSchema])).optional().default([]),
|
|
1620
1633
|
autoMerge: z.boolean().optional(),
|
|
1621
1634
|
mergeMode: z.enum(["merge", "replace"]).optional().default("merge")
|
|
@@ -3146,6 +3159,92 @@ const installContract = {
|
|
|
3146
3159
|
}).input(InstallFromGithubDTOSchema).output(InstallResultVOSchema)
|
|
3147
3160
|
};
|
|
3148
3161
|
//#endregion
|
|
3162
|
+
//#region ../../packages/busabase-contract/src/domains/templates/types.ts
|
|
3163
|
+
/**
|
|
3164
|
+
* Template Center catalog types (pure zod, client-safe).
|
|
3165
|
+
*
|
|
3166
|
+
* The catalog is the file `busabase-cli index` builds from a skills repository
|
|
3167
|
+
* — see `busabase-package/index-build`. It is re-declared here rather than
|
|
3168
|
+
* imported because that module is Node-only (it reads packages), and these
|
|
3169
|
+
* shapes are rendered in a browser.
|
|
3170
|
+
*
|
|
3171
|
+
* Spec: `apps/busabase/content/spec/template-center.md` §6.4.
|
|
3172
|
+
*/
|
|
3173
|
+
const TemplateStatsVOSchema = z.object({
|
|
3174
|
+
folders: z.number().int(),
|
|
3175
|
+
docs: z.number().int(),
|
|
3176
|
+
bases: z.number().int(),
|
|
3177
|
+
records: z.number().int(),
|
|
3178
|
+
files: z.number().int(),
|
|
3179
|
+
airapps: z.number().int(),
|
|
3180
|
+
skill: z.boolean()
|
|
3181
|
+
});
|
|
3182
|
+
const TemplateCardVOSchema = z.object({
|
|
3183
|
+
/** Stable across a catalog: `<repo>/<subdir>`. What a route keys on. */
|
|
3184
|
+
id: z.string(),
|
|
3185
|
+
name: z.string(),
|
|
3186
|
+
description: z.string(),
|
|
3187
|
+
category: z.string(),
|
|
3188
|
+
tags: z.array(z.string()).default([]),
|
|
3189
|
+
/**
|
|
3190
|
+
* Absolute URLs, resolved server-side.
|
|
3191
|
+
*
|
|
3192
|
+
* The catalog stores package-relative paths; turning them into URLs needs to
|
|
3193
|
+
* know the repo and ref, which the server already has and the browser would
|
|
3194
|
+
* otherwise have to re-derive. Doing it once here also means a card cannot
|
|
3195
|
+
* accidentally point at a different ref than the one it installs.
|
|
3196
|
+
*/
|
|
3197
|
+
screenshots: z.array(z.string()).default([]),
|
|
3198
|
+
agentPrompts: z.array(z.string()).default([]),
|
|
3199
|
+
version: z.string().optional(),
|
|
3200
|
+
author: z.string().optional(),
|
|
3201
|
+
license: z.string().optional(),
|
|
3202
|
+
stats: TemplateStatsVOSchema,
|
|
3203
|
+
/** Exactly what the install dialog needs — no URL assembly in the client. */
|
|
3204
|
+
install: z.object({
|
|
3205
|
+
repoUrl: z.string(),
|
|
3206
|
+
intoFolder: z.string()
|
|
3207
|
+
}),
|
|
3208
|
+
/** Where a curious user goes to read it before installing. */
|
|
3209
|
+
sourceUrl: z.string()
|
|
3210
|
+
});
|
|
3211
|
+
const TemplateCatalogVOSchema = z.object({
|
|
3212
|
+
templates: z.array(TemplateCardVOSchema),
|
|
3213
|
+
/** `owner/repo` and ref the catalog was built from — shown as provenance. */
|
|
3214
|
+
repo: z.string(),
|
|
3215
|
+
ref: z.string(),
|
|
3216
|
+
/**
|
|
3217
|
+
* Why the catalog is empty or stale, in the server's own words.
|
|
3218
|
+
*
|
|
3219
|
+
* A gallery that silently shows nothing is indistinguishable from one that is
|
|
3220
|
+
* broken, and the difference matters: "the catalog could not be fetched" is a
|
|
3221
|
+
* thing a user can act on, "no templates" is not.
|
|
3222
|
+
*/
|
|
3223
|
+
error: z.string().optional()
|
|
3224
|
+
});
|
|
3225
|
+
const ListTemplatesDTOSchema = z.object({
|
|
3226
|
+
/** Bypass the cache — the refresh button. */
|
|
3227
|
+
refresh: z.boolean().optional() }).optional().default({});
|
|
3228
|
+
//#endregion
|
|
3229
|
+
//#region ../../packages/busabase-contract/src/domains/templates/contract.ts
|
|
3230
|
+
/**
|
|
3231
|
+
* Template Center — the catalog a user browses before installing.
|
|
3232
|
+
*
|
|
3233
|
+
* Read-only and server-side on purpose. The catalog lives in a GitHub
|
|
3234
|
+
* repository, and a browser fetching it directly would hit CORS, would have no
|
|
3235
|
+
* cache shared between users, and would let the page decide which host to trust.
|
|
3236
|
+
* Installing is NOT here: a card's button hands its URL to the existing
|
|
3237
|
+
* `install.*` routes, so browsing and installing cannot disagree about what a
|
|
3238
|
+
* package is or who is allowed to install it.
|
|
3239
|
+
*/
|
|
3240
|
+
const templatesContract = { list: oc.route({
|
|
3241
|
+
method: "GET",
|
|
3242
|
+
path: "/templates",
|
|
3243
|
+
tags: ["Templates"],
|
|
3244
|
+
summary: "List the Template Center catalog",
|
|
3245
|
+
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."
|
|
3246
|
+
}).input(ListTemplatesDTOSchema).output(TemplateCatalogVOSchema) };
|
|
3247
|
+
//#endregion
|
|
3149
3248
|
//#region ../../packages/busabase-contract/src/domains/vault/types.ts
|
|
3150
3249
|
const VaultItemKeySchema = z.string().trim().min(1).max(128).regex(/^[A-Z_][A-Z0-9_]*$/, "Use uppercase letters, numbers, and underscores");
|
|
3151
3250
|
const VaultItemValueSchema = z.string().max(8192);
|
|
@@ -4210,6 +4309,7 @@ const busabaseContractRoutes = {
|
|
|
4210
4309
|
webhooks: webhookContract,
|
|
4211
4310
|
dump: dumpContract,
|
|
4212
4311
|
install: installContract,
|
|
4312
|
+
templates: templatesContract,
|
|
4213
4313
|
changeRequests: {
|
|
4214
4314
|
list: oc.route({
|
|
4215
4315
|
method: "GET",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "busabase-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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",
|
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
"tsx": "^4.20.5",
|
|
64
64
|
"typescript": "^7.0.2",
|
|
65
65
|
"vitest": "^2.1.8",
|
|
66
|
-
"busabase-contract": "0.
|
|
67
|
-
"
|
|
68
|
-
"
|
|
66
|
+
"busabase-contract": "0.19.0",
|
|
67
|
+
"openlib": "0.1.1",
|
|
68
|
+
"open-domains": "0.0.2"
|
|
69
69
|
},
|
|
70
70
|
"engines": {
|
|
71
71
|
"node": ">=24.18.0"
|