@avocadostudio-ai/site-sdk 0.1.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.
Files changed (63) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +145 -0
  3. package/dist/cli/register.d.ts +33 -0
  4. package/dist/cli/register.js +315 -0
  5. package/dist/create-site-page.d.ts +95 -0
  6. package/dist/create-site-page.js +127 -0
  7. package/dist/draft-common.d.ts +4 -0
  8. package/dist/draft-common.js +17 -0
  9. package/dist/draft-context-core.d.ts +11 -0
  10. package/dist/draft-context-core.js +26 -0
  11. package/dist/draft-context.d.ts +8 -0
  12. package/dist/draft-context.js +14 -0
  13. package/dist/draft-fetch.d.ts +14 -0
  14. package/dist/draft-fetch.js +115 -0
  15. package/dist/draft-routes-core.d.ts +11 -0
  16. package/dist/draft-routes-core.js +41 -0
  17. package/dist/draft-routes.d.ts +2 -0
  18. package/dist/draft-routes.js +27 -0
  19. package/dist/draft.d.ts +3 -0
  20. package/dist/draft.js +6 -0
  21. package/dist/editor-api-handler.d.ts +59 -0
  22. package/dist/editor-api-handler.js +89 -0
  23. package/dist/editor-cors.d.ts +3 -0
  24. package/dist/editor-cors.js +31 -0
  25. package/dist/editor-manifest.d.ts +3 -0
  26. package/dist/editor-manifest.js +65 -0
  27. package/dist/editor-overlay-inner.d.ts +5 -0
  28. package/dist/editor-overlay-inner.js +7 -0
  29. package/dist/editor-overlay.d.ts +4 -0
  30. package/dist/editor-overlay.js +14 -0
  31. package/dist/editor-query.d.ts +2 -0
  32. package/dist/editor-query.js +16 -0
  33. package/dist/editor-routes.d.ts +35 -0
  34. package/dist/editor-routes.js +66 -0
  35. package/dist/editor.d.ts +20 -0
  36. package/dist/editor.js +22 -0
  37. package/dist/index.d.ts +5 -0
  38. package/dist/index.js +5 -0
  39. package/dist/integration-check.d.ts +5 -0
  40. package/dist/integration-check.js +29 -0
  41. package/dist/live-preview-blocks.d.ts +5 -0
  42. package/dist/live-preview-blocks.js +30 -0
  43. package/dist/manifest-utils.d.ts +17 -0
  44. package/dist/manifest-utils.js +44 -0
  45. package/dist/middleware.d.ts +37 -0
  46. package/dist/middleware.js +34 -0
  47. package/dist/navigation.d.ts +48 -0
  48. package/dist/navigation.js +95 -0
  49. package/dist/publish-handlers/json-file.d.ts +24 -0
  50. package/dist/publish-handlers/json-file.js +40 -0
  51. package/dist/publish-utils.d.ts +28 -0
  52. package/dist/publish-utils.js +92 -0
  53. package/dist/render-blocks.d.ts +4 -0
  54. package/dist/render-blocks.js +26 -0
  55. package/dist/revalidate-handler.d.ts +42 -0
  56. package/dist/revalidate-handler.js +77 -0
  57. package/dist/routes.d.ts +10 -0
  58. package/dist/routes.js +12 -0
  59. package/dist/server/orchestrator.d.ts +117 -0
  60. package/dist/server/orchestrator.js +733 -0
  61. package/dist/types.d.ts +8 -0
  62. package/dist/types.js +1 -0
  63. package/package.json +104 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Factory for CMS webhook → ISR revalidation + orchestrator bootstrap routes.
3
+ *
4
+ * All three CMS integrations follow the same pattern:
5
+ * 1. Validate webhook secret
6
+ * 2. Extract slug from CMS-specific body shape
7
+ * 3. Call Next.js revalidatePath()
8
+ * 4. Re-bootstrap orchestrator with fresh content
9
+ *
10
+ * This factory extracts that into a single configurable handler.
11
+ *
12
+ * Usage:
13
+ * ```ts
14
+ * export const POST = createRevalidateHandler({
15
+ * secretEnvVar: "SANITY_WEBHOOK_SECRET",
16
+ * extractSlug: (body) => (body as any)?.slug?.current ?? null,
17
+ * getPages: getSanityPages,
18
+ * siteId: "sanity-site",
19
+ * })
20
+ * ```
21
+ */
22
+ export function createRevalidateHandler(config) {
23
+ const { secretEnvVar, extractSlug, getPages, siteId, session = "dev" } = config;
24
+ const secretHeader = config.secretHeader === undefined ? "x-revalidate-secret" : config.secretHeader;
25
+ return async function POST(request) {
26
+ const configuredSecret = process.env[secretEnvVar]?.trim();
27
+ if (!configuredSecret) {
28
+ return Response.json({ error: `${secretEnvVar} not configured` }, { status: 500 });
29
+ }
30
+ // Validate secret: check configured sources in order
31
+ let provided;
32
+ const sources = secretHeader === null ? ["query:secret"]
33
+ : Array.isArray(secretHeader) ? secretHeader
34
+ : [secretHeader];
35
+ const url = new URL(request.url);
36
+ for (const src of sources) {
37
+ if (src.startsWith("query:")) {
38
+ provided = url.searchParams.get(src.slice(6))?.trim() ?? undefined;
39
+ }
40
+ else {
41
+ provided = request.headers.get(src)?.trim() ?? undefined;
42
+ }
43
+ if (provided)
44
+ break;
45
+ }
46
+ if (!provided || provided !== configuredSecret) {
47
+ return Response.json({ error: "Invalid secret" }, { status: 401 });
48
+ }
49
+ try {
50
+ const body = await request.json();
51
+ const rawSlug = extractSlug(body) ?? "/";
52
+ const path = rawSlug === "/" ? "/" : `/${rawSlug.replace(/^\//, "")}`;
53
+ // ISR revalidation — dynamic import to avoid hard dep on next
54
+ try {
55
+ const { revalidatePath } = await import(/* webpackIgnore: true */ "next/cache");
56
+ revalidatePath(path);
57
+ }
58
+ catch {
59
+ // Not running in Next.js or revalidatePath unavailable
60
+ }
61
+ // Re-bootstrap orchestrator with fresh CMS content
62
+ const orchestratorUrl = process.env.ORCHESTRATOR_URL;
63
+ if (orchestratorUrl) {
64
+ const pages = await getPages();
65
+ await fetch(`${orchestratorUrl}/draft/bootstrap`, {
66
+ method: "POST",
67
+ headers: { "content-type": "application/json" },
68
+ body: JSON.stringify({ session, siteId, pages, overwrite: true }),
69
+ }).catch(() => { });
70
+ }
71
+ return Response.json({ revalidated: true, path });
72
+ }
73
+ catch {
74
+ return Response.json({ error: "Invalid request body" }, { status: 400 });
75
+ }
76
+ };
77
+ }
@@ -0,0 +1,10 @@
1
+ export { createDraftEnableHandler, createDraftDisableHandler } from "./draft-routes.ts";
2
+ export { createBlocksHandler, createPagesHandler, createPublishHandler } from "./editor-routes.ts";
3
+ export type { OnPublishFn, InlineAsset, PublishContext } from "./editor-routes.ts";
4
+ export { createEditorApiHandler } from "./editor-api-handler.ts";
5
+ export type { EditorApiHandlerConfig } from "./editor-api-handler.ts";
6
+ export { isSafeImageUrl, createImageResolver } from "./publish-utils.ts";
7
+ export type { ImageUploader } from "./publish-utils.ts";
8
+ export { createRevalidateHandler } from "./revalidate-handler.ts";
9
+ export type { RevalidateHandlerConfig } from "./revalidate-handler.ts";
10
+ export { getManifestImageFields } from "./manifest-utils.ts";
package/dist/routes.js ADDED
@@ -0,0 +1,12 @@
1
+ // Draft route handler factories
2
+ export { createDraftEnableHandler, createDraftDisableHandler } from "./draft-routes.js";
3
+ // Editor route handler factories
4
+ export { createBlocksHandler, createPagesHandler, createPublishHandler } from "./editor-routes.js";
5
+ // Catch-all editor API handler
6
+ export { createEditorApiHandler } from "./editor-api-handler.js";
7
+ // Publish utilities (SSRF check, image resolution)
8
+ export { isSafeImageUrl, createImageResolver } from "./publish-utils.js";
9
+ // Revalidation handler factory
10
+ export { createRevalidateHandler } from "./revalidate-handler.js";
11
+ // Manifest utilities (derive image fields from block manifest)
12
+ export { getManifestImageFields } from "./manifest-utils.js";
@@ -0,0 +1,117 @@
1
+ import { type AIProvider, type ModelKey } from "@avocadostudio-ai/orchestrator-core/state/session-state.js";
2
+ import { type Logger } from "@avocadostudio-ai/orchestrator-core/logger.js";
3
+ import type { CmsAdapter } from "@avocadostudio-ai/orchestrator-core/cms/adapter.js";
4
+ export interface CreateOrchestratorConfig {
5
+ /**
6
+ * Provider model overrides. Defaults to env vars (OPENAI_MODEL_*, ANTHROPIC_MODEL_*,
7
+ * GOOGLE_GENAI_MODEL_*). Pass an explicit object to override per-tier model names.
8
+ */
9
+ modelLookup?: Record<AIProvider, Record<ModelKey, string>>;
10
+ /**
11
+ * Override available providers. Defaults to whichever API keys are present in
12
+ * process.env (OPENAI_API_KEY / ANTHROPIC_API_KEY / GOOGLE_GENAI_API_KEY).
13
+ */
14
+ availableProviders?: AIProvider[];
15
+ /** Pino-shaped logger. Defaults to a console-backed implementation. */
16
+ logger?: Logger;
17
+ /**
18
+ * Allowed CORS origins. Defaults to "*". For production, pass an explicit
19
+ * allow-list (Next.js can also handle CORS via middleware — pass null here
20
+ * to disable the SDK's CORS handling entirely).
21
+ */
22
+ corsOrigins?: string[] | "*" | null;
23
+ /**
24
+ * Register the default builtin tools (unsplash-search, image-generate,
25
+ * gdrive-browse). Defaults to `false` — opt in only if you want those
26
+ * features, since registering them pulls in sharp + googleapis + image-gen
27
+ * SDKs at module load. Pass `true` for env-gated defaults, or an explicit
28
+ * subset like `["unsplash-search"]`.
29
+ */
30
+ builtinTools?: boolean | Array<"unsplash-search" | "image-generate" | "gdrive-browse">;
31
+ /**
32
+ * URL path prefix the handler is mounted under. Everything matching this
33
+ * prefix is stripped off `url.pathname` before route matching, so the rest
34
+ * of the handler can reason in terms of `/chat`, `/chat/stream`, etc.
35
+ *
36
+ * Defaults to `/api/avocado` (the catch-all path used in the Next.js
37
+ * example app). Set to `""` if you want to match against the full pathname
38
+ * yourself, or override to e.g. `/api/orchestrator` if you mount the
39
+ * catch-all there.
40
+ */
41
+ basePath?: string;
42
+ /**
43
+ * Source-of-truth adapter for the site's existing content. Called once per
44
+ * session on the first chat request to seed SQLite with the site's pages.
45
+ * Without an adapter the orchestrator starts with an empty draft, which
46
+ * means the planner has no pages to edit — every "edit the homepage" turn
47
+ * returns `page not found`.
48
+ *
49
+ * Provided implementations:
50
+ * - `jsonFileAdapter({ path })` — read PageDoc[] from a JSON file
51
+ * - `editorApiAdapter({ origin })` — fetch from `${origin}/api/editor/pages`
52
+ *
53
+ * Import from `@avocadostudio-ai/orchestrator-core/cms`.
54
+ */
55
+ adapter?: CmsAdapter;
56
+ /**
57
+ * Site identifier used to scope session state in SQLite. When `adapter` is
58
+ * set, defaults to `"library"` so the demo-content seed path is bypassed
59
+ * (an unscoped session falls back to the orchestrator's bundled demo pages).
60
+ * Override if you want to run multiple distinct sites against one process.
61
+ *
62
+ * Precedence on incoming requests:
63
+ * - adapter configured: `siteId` (or its `"library"` default) ALWAYS wins
64
+ * over `body.siteId`. The library-mode orchestrator owns its identity.
65
+ * - no adapter: explicit `siteId` wins; otherwise `body.siteId` is used.
66
+ */
67
+ siteId?: string;
68
+ /**
69
+ * Register the site's block schemas before the orchestrator's first chat
70
+ * request. Use this instead of relying on side-effect import order: in
71
+ * library mode, the orchestrator's transitive imports of
72
+ * `@avocadostudio-ai/shared` re-register canonical Hero/CTA/etc. on the
73
+ * shared globalThis registry, often AFTER the host app's overrides.
74
+ *
75
+ * **Fires once per orchestrator runtime** — at `buildRuntime` time, not
76
+ * per-request. For long-lived production processes this is fine, but if
77
+ * the host re-builds the runtime on hot-reload, ensure the schemas survive
78
+ * (registerBlock is idempotent — calling again is safe). Pair this with
79
+ * the same `registerBlocks` passed to `createEditorApiHandler`, which
80
+ * re-runs on every `/blocks` request, to cover request-time too.
81
+ */
82
+ registerBlocks?: () => void;
83
+ /**
84
+ * Local directory where `POST /image/upload` writes uploaded files and
85
+ * `GET /generated-images/:fileName` reads them back. Defaults to
86
+ * `.data/generated-images` under the host process CWD.
87
+ *
88
+ * **POC-grade storage.** Files live on the orchestrator host's local disk
89
+ * with no CDN, no image transforms, and no durability guarantee — on an
90
+ * ephemeral filesystem (e.g. a container without a mounted volume) uploads
91
+ * vanish on redeploy. See `docs/image-storage-options.md` for the path to a
92
+ * blob/CDN backend; that swap is contained to these two routes.
93
+ */
94
+ imageDir?: string;
95
+ }
96
+ /**
97
+ * A handler returned by {@link createOrchestrator}. Callable like the bare
98
+ * Web `(Request) => Promise<Response>` it always was, with an extra
99
+ * `dispose()` method to release the resumable-stream sweep timer (call this
100
+ * during dev hot-reload or test teardown).
101
+ */
102
+ export type OrchestratorHandler = ((request: Request) => Promise<Response>) & {
103
+ dispose(): Promise<void>;
104
+ };
105
+ export type { CmsAdapter, CmsInlineAsset, CmsPublishContext, CmsPublishResult } from "@avocadostudio-ai/orchestrator-core/cms/adapter.js";
106
+ export { jsonFileAdapter, editorApiAdapter } from "@avocadostudio-ai/orchestrator-core/cms/index.js";
107
+ /**
108
+ * Build a Web-standard request handler that wraps the orchestrator brain.
109
+ *
110
+ * Usage in Next.js App Router (`app/api/avocado/[[...path]]/route.ts`):
111
+ *
112
+ * export const runtime = "nodejs"
113
+ * const handler = createOrchestrator()
114
+ * export const POST = handler
115
+ * export const OPTIONS = handler
116
+ */
117
+ export declare function createOrchestrator(config?: CreateOrchestratorConfig): OrchestratorHandler;