@avocadostudio-ai/site-sdk 0.2.1 → 0.2.3
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/README.md +283 -11
- package/package.json +16 -5
package/README.md
CHANGED
|
@@ -10,7 +10,66 @@ SDK for integrating any Next.js site with the Avocado Studio. Provides the contr
|
|
|
10
10
|
npm install @avocadostudio-ai/site-sdk
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
### 2.
|
|
13
|
+
### 2. Wrap your Next config
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// next.config.ts
|
|
17
|
+
import { withAvocado } from "@avocadostudio-ai/site-sdk/next-config"
|
|
18
|
+
|
|
19
|
+
export default withAvocado({ /* your config */ })
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Not optional, and the failures it prevents all look like something else:
|
|
23
|
+
|
|
24
|
+
- **Native binaries and provider SDKs get bundled.** `better-sqlite3` and
|
|
25
|
+
`sharp` build fine and die loading their `.node` file on the first request;
|
|
26
|
+
Turbopack resolves `orchestrator-core`'s optional `await import(...)` peers
|
|
27
|
+
statically and fails the build over a package you deliberately never
|
|
28
|
+
installed. `serverExternalPackages` alone does not fix it — `transpilePackages`
|
|
29
|
+
overrides server externals for a *transitive* dependency. This sets both.
|
|
30
|
+
- **Generated images 500.** Avocado writes image URLs from hosts your site never
|
|
31
|
+
chose (Unsplash, the image models' blob storage), and `next/image` refuses any
|
|
32
|
+
host missing from `images.remotePatterns`.
|
|
33
|
+
- **`trailingSlash: true` makes the editor unreachable** — see below.
|
|
34
|
+
|
|
35
|
+
Pass `{ images: false }`, `{ serverExternals: false }` or
|
|
36
|
+
`{ trailingSlash: false }` as a second argument to manage any of them yourself.
|
|
37
|
+
|
|
38
|
+
#### If your site sets `trailingSlash: true`, read this
|
|
39
|
+
|
|
40
|
+
Next applies its trailing-slash 308 to `/api/*` as well, so
|
|
41
|
+
`/api/editor/blocks` answers `308 → /api/editor/blocks/`. `fetch` follows a 308.
|
|
42
|
+
**A CORS preflight does not** — a browser treats a redirect on `OPTIONS` as a
|
|
43
|
+
network failure — and the editor calls those routes from its own origin. Every
|
|
44
|
+
editor API call therefore fails before it is sent, and the browser reports a
|
|
45
|
+
generic CORS error that names nothing. Middleware cannot repair it either: the
|
|
46
|
+
redirect runs before middleware.
|
|
47
|
+
|
|
48
|
+
`withAvocado` sets `skipTrailingSlashRedirect: true` for you. **That half alone
|
|
49
|
+
is a regression** — it stops your site redirecting `/about` to `/about/`, on
|
|
50
|
+
every URL you have ever published. The SDK's proxy puts the redirect back for
|
|
51
|
+
page routes, and you have to turn it on:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// proxy.ts (middleware.ts on Next 15)
|
|
55
|
+
import { createEditorProxy } from "@avocadostudio-ai/site-sdk/proxy"
|
|
56
|
+
|
|
57
|
+
export const proxy = createEditorProxy({ trailingSlash: true }).proxy
|
|
58
|
+
|
|
59
|
+
export const config = {
|
|
60
|
+
matcher: ["/((?!_next|preview-draft|api|favicon\\.ico|icon\\.svg|logos/|generated-images/|.*\\.).*)"],
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
These two are a pair. Nothing checks that you set both, because the config
|
|
65
|
+
cannot see your proxy and the proxy cannot read the config.
|
|
66
|
+
|
|
67
|
+
> Do not hand-roll that redirect from `request.nextUrl.clone()`. NextURL
|
|
68
|
+
> normalises the trailing slash back *off* when it stringifies, so the 308
|
|
69
|
+
> points at the URL it is trying to leave and the browser gives up after five
|
|
70
|
+
> hops. `createEditorProxy` builds it from `request.url`.
|
|
71
|
+
|
|
72
|
+
### 3. Create the page component
|
|
14
73
|
|
|
15
74
|
```tsx
|
|
16
75
|
// app/[[...slug]]/page.tsx
|
|
@@ -38,7 +97,7 @@ Export `generateMetadata` too, or every page inherits the root layout's
|
|
|
38
97
|
from the page and calls `notFound()` for an unknown slug, so add an
|
|
39
98
|
`app/not-found.tsx` if you want your own chrome around the 404.
|
|
40
99
|
|
|
41
|
-
###
|
|
100
|
+
### 4. Create the editor API route
|
|
42
101
|
|
|
43
102
|
```tsx
|
|
44
103
|
// app/api/editor/[...path]/route.ts
|
|
@@ -55,14 +114,14 @@ export const { GET, POST, OPTIONS } = createEditorApiHandler({
|
|
|
55
114
|
})
|
|
56
115
|
```
|
|
57
116
|
|
|
58
|
-
###
|
|
117
|
+
### 5. Add styles
|
|
59
118
|
|
|
60
119
|
```tsx
|
|
61
120
|
// app/layout.tsx
|
|
62
121
|
import "@avocadostudio-ai/blocks/styles.css"
|
|
63
122
|
```
|
|
64
123
|
|
|
65
|
-
###
|
|
124
|
+
### 6. Set environment variables
|
|
66
125
|
|
|
67
126
|
```env
|
|
68
127
|
# Required
|
|
@@ -86,14 +145,34 @@ That's it. Your site now works with the AI editor.
|
|
|
86
145
|
|
|
87
146
|
## Exports
|
|
88
147
|
|
|
148
|
+
All seventeen, because the six this table used to list were not the six an
|
|
149
|
+
integration needs — `createOrchestrator` is the whole of library mode and was in
|
|
150
|
+
none of them.
|
|
151
|
+
|
|
89
152
|
| Import | What it provides |
|
|
90
153
|
|---|---|
|
|
91
154
|
| `@avocadostudio-ai/site-sdk` | Types (`PageDoc`, `BlockInstance`), `buildSlug`, `renderBlocks` |
|
|
92
|
-
|
|
|
93
|
-
|
|
|
94
|
-
|
|
|
95
|
-
|
|
|
96
|
-
|
|
|
155
|
+
| `.../page` | `createSitePage` — page component factory |
|
|
156
|
+
| `.../routes` | `createEditorApiHandler` — the `/api/editor/*` route factory |
|
|
157
|
+
| `.../routes/core` | The same handlers without Next's `draftMode()`, for a non-Next host |
|
|
158
|
+
| `.../server` | **`createOrchestrator`**, `jsonFileAdapter`, `editorApiAdapter`, `resolveCapabilities`, and the `CmsAdapter` / `CreateOrchestratorConfig` types |
|
|
159
|
+
| `.../next-config` | `withAvocado`, `AVOCADO_IMAGE_HOSTS`, `AVOCADO_SERVER_EXTERNALS` |
|
|
160
|
+
| `.../proxy` | `createEditorProxy` — the Next 16 `proxy.ts` rewrite |
|
|
161
|
+
| `.../middleware` | `createEditorMiddleware` — the same thing under the Next 15 name |
|
|
162
|
+
| `.../matcher` | `buildEditorMatcher` — just the matcher string, no `next/server` import |
|
|
163
|
+
| `.../draft` | `resolveEditorContext`, `fetchEditorPage`, `fetchEditorSlugs`, `fetchEditorSiteConfig` |
|
|
164
|
+
| `.../draft/core` | The draft context without Next's cookie APIs |
|
|
165
|
+
| `.../editor` | `EditorOverlay`, `LivePreviewProvider`, `buildEditorQuerySuffix` |
|
|
166
|
+
| `.../editor-manifest` | `buildBlockManifest` — the built-in blocks as a manifest |
|
|
167
|
+
| `.../publish` | `diffPage`, `groupPatches`, `describeUnsupported` — the field-level publish walk |
|
|
168
|
+
| `.../publish-handlers/json-file` | A ready-made `onPublish` that writes a JSON file |
|
|
169
|
+
| `.../navigation` | `buildNavItems`, `buildSiteHeaderBlock` |
|
|
170
|
+
| `.../seo` | `buildPageMetadata` and the title/description derivation |
|
|
171
|
+
|
|
172
|
+
`useLivePreviewBlocks` — the hook that reads `LivePreviewProvider`, and the only
|
|
173
|
+
way to render a live draft through **your own** components rather than Avocado's
|
|
174
|
+
— is not here. It lives in `@avocadostudio-ai/preview-adapter`, which you have to
|
|
175
|
+
install directly.
|
|
97
176
|
|
|
98
177
|
## API Contract
|
|
99
178
|
|
|
@@ -107,6 +186,84 @@ Your site exposes these endpoints via `createEditorApiHandler`:
|
|
|
107
186
|
| `/api/editor/pages` | GET | Return all published pages |
|
|
108
187
|
| `/api/editor/publish` | POST | Receive pages from editor, persist to CMS |
|
|
109
188
|
|
|
189
|
+
## Library mode mounts two handlers, not one
|
|
190
|
+
|
|
191
|
+
`createOrchestrator` is not a replacement for `createEditorApiHandler`. It is an
|
|
192
|
+
addition, and the editor does not work without both.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
// app/api/avocado/[[...path]]/route.ts — the orchestrator
|
|
196
|
+
import { createOrchestrator } from "@avocadostudio-ai/site-sdk/server"
|
|
197
|
+
|
|
198
|
+
const handler = createOrchestrator({ basePath: "/api/avocado", adapter: myAdapter })
|
|
199
|
+
export const GET = handler
|
|
200
|
+
export const POST = handler
|
|
201
|
+
export const OPTIONS = handler
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
// app/api/editor/[...path]/route.ts — still required
|
|
206
|
+
import { createEditorApiHandler } from "@avocadostudio-ai/site-sdk/routes"
|
|
207
|
+
|
|
208
|
+
export const { GET, POST, OPTIONS } = createEditorApiHandler({ getPages, getManifest })
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The editor UI asks the orchestrator for plans and operations, and asks **your
|
|
212
|
+
site's own origin** for `/api/editor/blocks` and `/api/editor/pages`. Mount only
|
|
213
|
+
the orchestrator and the editor loads, connects, and answers four requests with
|
|
214
|
+
`net::ERR_FAILED` in a console you have to open to see.
|
|
215
|
+
|
|
216
|
+
## The adapter contract
|
|
217
|
+
|
|
218
|
+
`createOrchestrator({ adapter })` is the whole of library mode: SQLite is the
|
|
219
|
+
working copy for drafts, undo and chat state, and the adapter is your actual
|
|
220
|
+
content store. It is read on a cold session and written on publish.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import type { CmsAdapter } from "@avocadostudio-ai/site-sdk/server"
|
|
224
|
+
|
|
225
|
+
const myAdapter: CmsAdapter = {
|
|
226
|
+
/** Short, DNS-safe. Used for telemetry and the bootstrap cache. */
|
|
227
|
+
id: "sanity",
|
|
228
|
+
|
|
229
|
+
/** Does `getPages` honour `options.perspective`? Silence means no. */
|
|
230
|
+
perspectives: Boolean(process.env.SANITY_API_READ_TOKEN),
|
|
231
|
+
|
|
232
|
+
/** Every page for this site. Failures are logged, not fatal — the session starts empty. */
|
|
233
|
+
async getPages(options) {
|
|
234
|
+
return fetchPages({ draft: options?.perspective === "draft" })
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
/** Optional. Absent means publish is a local no-op. */
|
|
238
|
+
async onPublish(pages, config, context) {
|
|
239
|
+
// context.published — what you last reported, when the orchestrator has it
|
|
240
|
+
// context.assets — base64 blobs from chat-generated images, for you to upload
|
|
241
|
+
return { ok: true, unsupported: [] }
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
/** Static, because /whoami must answer it with the CMS unreachable. */
|
|
245
|
+
capabilities: { createPage: false },
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Two parts of that are easy to get wrong:
|
|
250
|
+
|
|
251
|
+
**`perspectives` defaults to "no", and the other flags default to "yes".** That
|
|
252
|
+
asymmetry is deliberate: capabilities are permissions, where the safe answer is
|
|
253
|
+
to allow what nobody forbade; `perspectives` is an ability, where the safe answer
|
|
254
|
+
is not to claim one nobody implemented. An adapter wrongly believed to read
|
|
255
|
+
drafts makes the publish diff assert that unpublished work is live.
|
|
256
|
+
|
|
257
|
+
**`onPublish` should diff, not overwrite.** Every CMS read is a projection — an
|
|
258
|
+
asset reference flattened to a URL, rich text flattened to a string — and writing
|
|
259
|
+
the projection back destroys what it was projected from. `context.published` is
|
|
260
|
+
the baseline; `undefined` means *no baseline available*, never *the site was
|
|
261
|
+
empty*. See [Publishing back to a real CMS](#publishing-back-to-a-real-cms).
|
|
262
|
+
|
|
263
|
+
`CmsAdapter`, `CmsCapabilities`, `CmsPublishContext`, `CmsPerspective` and
|
|
264
|
+
`CreateOrchestratorConfig` are all exported from
|
|
265
|
+
`@avocadostudio-ai/site-sdk/server`.
|
|
266
|
+
|
|
110
267
|
## Library mode needs a credential
|
|
111
268
|
|
|
112
269
|
`createOrchestrator()` mounts on your own domain and can edit and publish your
|
|
@@ -220,11 +377,11 @@ string, a document reference resolved to one language's href, rich text
|
|
|
220
377
|
flattened to markdown. Writing the projection back replaces the reference with
|
|
221
378
|
the flattening and destroys the document.
|
|
222
379
|
|
|
223
|
-
So publish a **diff**, not a snapshot. `@ai
|
|
380
|
+
So publish a **diff**, not a snapshot. `@avocadostudio-ai/site-sdk/publish` owns
|
|
224
381
|
the walk:
|
|
225
382
|
|
|
226
383
|
```ts
|
|
227
|
-
import { diffPage, groupPatches, describeUnsupported } from "@ai
|
|
384
|
+
import { diffPage, groupPatches, describeUnsupported } from "@avocadostudio-ai/site-sdk/publish"
|
|
228
385
|
|
|
229
386
|
const diff = diffPage({
|
|
230
387
|
page,
|
|
@@ -281,6 +438,88 @@ absent after a restart that reloaded the draft from storage. Treat `undefined`
|
|
|
281
438
|
as "no baseline available", never as "the site was empty": publishing every
|
|
282
439
|
field on that assumption is the overwrite all of this exists to prevent.
|
|
283
440
|
|
|
441
|
+
## Registering your own block schemas
|
|
442
|
+
|
|
443
|
+
The manifest at `/api/editor/blocks` tells the **editor** what exists. It is not
|
|
444
|
+
what validates an edit — that is the global block registry in
|
|
445
|
+
`@avocadostudio-ai/shared`, which starts out holding only Avocado's built-ins.
|
|
446
|
+
Ship a manifest and no registration and the editor looks completely wired up
|
|
447
|
+
until the first AI edit:
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
{"error":"Invalid props for PricingTable: Unknown block type: PricingTable","errorCode":"schema_violation"}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
// lib/register-blocks.ts
|
|
455
|
+
import { registerBlock, z } from "@avocadostudio-ai/shared"
|
|
456
|
+
|
|
457
|
+
export function registerMyBlocks() {
|
|
458
|
+
registerBlock("PricingTable", {
|
|
459
|
+
schema: z.object({
|
|
460
|
+
title: z.string().min(1),
|
|
461
|
+
// A list needs BOTH halves — see the warning below.
|
|
462
|
+
tiers: z.array(z.object({
|
|
463
|
+
name: z.string().min(1),
|
|
464
|
+
price: z.string().min(1),
|
|
465
|
+
})).optional(),
|
|
466
|
+
}).catchall(z.unknown()),
|
|
467
|
+
|
|
468
|
+
meta: {
|
|
469
|
+
displayName: "Pricing Table",
|
|
470
|
+
fields: { title: { kind: "text" } },
|
|
471
|
+
listFields: {
|
|
472
|
+
tiers: { label: "Tiers", itemFields: { name: { kind: "text" }, price: { kind: "text" } } },
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
})
|
|
476
|
+
}
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Hand that function to whichever handler you mount — both re-run it after the
|
|
480
|
+
built-ins have registered, so your definitions land on top:
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
createEditorApiHandler({ getPages, getManifest, registerBlocks: registerMyBlocks })
|
|
484
|
+
createOrchestrator({ adapter, registerBlocks: registerMyBlocks })
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
Do **not** instead rely on a side-effect `import "@/lib/register-blocks"` placed
|
|
488
|
+
last in the file. Next's bundler does not reliably preserve module order across
|
|
489
|
+
the RSC, SSR and route-handler layers, so the built-in schemas sometimes
|
|
490
|
+
re-register on top of yours. The hook exists to replace that trick.
|
|
491
|
+
|
|
492
|
+
### Import `z` from `@avocadostudio-ai/shared`, not from `zod`
|
|
493
|
+
|
|
494
|
+
`registerBlock` takes a `ZodObject`, and a Zod object is assignable only to one
|
|
495
|
+
built by the *same copy* of the library. Your own `import { z } from "zod"`
|
|
496
|
+
resolves to whatever your tree hoisted — on any site that also uses Sanity that
|
|
497
|
+
is zod 3 — and the mismatch reports as a structural type error listing methods
|
|
498
|
+
you have never called (`loose`, `safeExtend`, `exactPartial`, `def`, "and 21
|
|
499
|
+
more"), with nothing anywhere saying there are two copies of zod. Importing `z`
|
|
500
|
+
from `shared` gives you ours, and you need no direct `zod` dependency at all.
|
|
501
|
+
|
|
502
|
+
### `schema` and `meta` are two halves that must agree
|
|
503
|
+
|
|
504
|
+
`meta.listFields` says how to *label* a list's rows. The props schema is what
|
|
505
|
+
says the list exists, and the property panel renders rows from the schema — so a
|
|
506
|
+
list named only in the meta shows no rows and no Add control, with no error
|
|
507
|
+
anywhere. Validation passes, operations apply, the preview renders, publishing
|
|
508
|
+
diffs the rows correctly. The only symptom is an absence in one panel.
|
|
509
|
+
|
|
510
|
+
That failure hides especially well behind `.catchall(z.unknown())`, which most
|
|
511
|
+
CMS integrations need (see [Publishing back to a real CMS](#publishing-back-to-a-real-cms),
|
|
512
|
+
which recommends keeping a `__source` snapshot in block props): the catchall
|
|
513
|
+
swallows the undeclared array as an unmodelled extra.
|
|
514
|
+
|
|
515
|
+
`registerBlock` now warns when it sees the contradiction:
|
|
516
|
+
|
|
517
|
+
```
|
|
518
|
+
[avocado] PricingTable: meta.listFields declares "tiers" but the schema has no `tiers`.
|
|
519
|
+
The property panel renders list rows from the schema, so this list will show no rows
|
|
520
|
+
and no Add control. Declare it alongside the meta, e.g. tiers: z.array(z.object({ … })).optional()
|
|
521
|
+
```
|
|
522
|
+
|
|
284
523
|
## Telling the orchestrator which blocks you render
|
|
285
524
|
|
|
286
525
|
Importing anything from `@avocadostudio-ai/shared` registers Avocado's 18
|
|
@@ -309,6 +548,39 @@ Declaring a type you never registered is not silently dropped: it has no schema
|
|
|
309
548
|
to describe, so it cannot reach the manifest, and the orchestrator logs a warning
|
|
310
549
|
naming it the first time the manifest is served.
|
|
311
550
|
|
|
551
|
+
## Making fields editable
|
|
552
|
+
|
|
553
|
+
The manifest tells the editor which blocks exist and what props they take. It
|
|
554
|
+
does not tell it **where on the page a prop is rendered**, and nothing can derive
|
|
555
|
+
that — so a site whose components carry no annotation gets block selection, the
|
|
556
|
+
badge, move and delete, and not one editable field.
|
|
557
|
+
|
|
558
|
+
Put `data-editable-target` on the DOM node that renders each prop:
|
|
559
|
+
|
|
560
|
+
```tsx
|
|
561
|
+
<h1 data-editable-target="heading">{heading}</h1>
|
|
562
|
+
<p data-editable-target="subheading">{subheading}</p>
|
|
563
|
+
|
|
564
|
+
{cards.map((card, i) => (
|
|
565
|
+
<article key={card._key}>
|
|
566
|
+
<h3 data-editable-target={`cards[${i}].title`}>{card.title}</h3>
|
|
567
|
+
<img data-editable-target={`cards[${i}].imageUrl`} src={card.imageUrl} />
|
|
568
|
+
</article>
|
|
569
|
+
))}
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
The path grammar is the same one operations use: `heading` for a scalar,
|
|
573
|
+
`cards[0].title` for a list item's field, `cards[0].imageUrl` for its image.
|
|
574
|
+
|
|
575
|
+
This is the one part of an integration that **cannot** live in an integration
|
|
576
|
+
layer — it has to go inside your own components, one attribute per prop you want
|
|
577
|
+
editable. Budget for it: it is usually the largest single cost of adopting the
|
|
578
|
+
editor, and there is no way to add it from the outside.
|
|
579
|
+
|
|
580
|
+
Two optional siblings control the labels the overlay draws:
|
|
581
|
+
`data-editable-target-label` (the CSS `::before` tooltip) and
|
|
582
|
+
`data-editable-label` (the floating pill). Both default to the target path.
|
|
583
|
+
|
|
312
584
|
## Environment Variables
|
|
313
585
|
|
|
314
586
|
| Variable | Required | Description |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/site-sdk",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -107,16 +107,16 @@
|
|
|
107
107
|
],
|
|
108
108
|
"dependencies": {
|
|
109
109
|
"zod": "^4.3.6",
|
|
110
|
-
"@avocadostudio-ai/
|
|
111
|
-
"@avocadostudio-ai/
|
|
112
|
-
"@avocadostudio-ai/preview-adapter": "0.2.
|
|
110
|
+
"@avocadostudio-ai/blocks": "0.2.3",
|
|
111
|
+
"@avocadostudio-ai/shared": "0.2.3",
|
|
112
|
+
"@avocadostudio-ai/preview-adapter": "0.2.3"
|
|
113
113
|
},
|
|
114
114
|
"peerDependencies": {
|
|
115
115
|
"next": ">=15.0.0",
|
|
116
116
|
"react": ">=19.0.0",
|
|
117
117
|
"react-dom": ">=19.0.0",
|
|
118
118
|
"better-sqlite3": ">=12.0.0",
|
|
119
|
-
"@avocadostudio-ai/orchestrator-core": "^0.2.
|
|
119
|
+
"@avocadostudio-ai/orchestrator-core": "^0.2.3"
|
|
120
120
|
},
|
|
121
121
|
"peerDependenciesMeta": {
|
|
122
122
|
"@avocadostudio-ai/orchestrator-core": {
|
|
@@ -132,6 +132,17 @@
|
|
|
132
132
|
"typescript": "^5.7.3"
|
|
133
133
|
},
|
|
134
134
|
"description": "SDK for adding Avocado Studio AI editing and live preview to a Next.js site",
|
|
135
|
+
"keywords": [
|
|
136
|
+
"avocado",
|
|
137
|
+
"avocado-studio",
|
|
138
|
+
"nextjs",
|
|
139
|
+
"cms",
|
|
140
|
+
"visual-editing",
|
|
141
|
+
"page-builder",
|
|
142
|
+
"ai",
|
|
143
|
+
"content-editor",
|
|
144
|
+
"draft-mode"
|
|
145
|
+
],
|
|
135
146
|
"license": "Apache-2.0",
|
|
136
147
|
"homepage": "https://github.com/avocadostudio-ai/avocado/tree/main/packages/site-sdk#readme",
|
|
137
148
|
"bugs": {
|