@224industries/webflow-ai-sdk 0.0.0 → 2.0.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/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # Webflow - AI SDK Tools
1
+ # Webflow - AI SDK Tools and Agents
2
2
 
3
3
  ![224 Industries OSS](https://img.shields.io/badge/224_Industries-OSS-111212?style=for-the-badge&labelColor=6AFFDC)
4
4
  ![MIT License](https://img.shields.io/badge/License-MIT-111212?style=for-the-badge&labelColor=6AFFDC)
5
5
  [![Webflow Premium Partner](https://img.shields.io/badge/Premium_Partner-146EF5?style=for-the-badge&logo=webflow&logoColor=white)](https://webflow.com/@224-industries)
6
6
  ![Vercel AI SDK](https://img.shields.io/badge/Vercel-AI%20SDK-000000?style=for-the-badge&logo=vercel&logoColor=white)
7
7
 
8
- A collection of [AI SDK](https://ai-sdk.dev) tools that give your AI agents the ability to manage [Webflow](https://webflow.com) sites, pages, forms, and custom code.
8
+ Give your AI agents the power to list and publish sites, manage pages, retrieve form submissions, and even add custom code to your Webflow projects. Pre-built agents like the `LeadResponseAgent` can automatically process form submissions and send response emails using [Resend](https://resend.com) templates.
9
9
 
10
10
  ## Installation
11
11
 
@@ -20,23 +20,43 @@ Set the following environment variables:
20
20
  ```bash
21
21
  WEBFLOW_API_KEY="your_webflow_api_key"
22
22
  WEBFLOW_SITE_ID="your_default_site_id"
23
+
24
+ # Required for LeadResponseAgent (uses Resend via `resend-ai-sdk` tools)
25
+ RESEND_API_KEY="your_resend_api_key"
26
+ RESEND_EMAIL_DOMAIN="your_verified_domain"
23
27
  ```
24
- Get your API key from the [Webflow Dashboard](https://webflow.com/dashboard).
28
+
29
+ Get your Webflow API key from the [Webflow Dashboard](https://webflow.com/dashboard) and your Resend API key from the [Resend Dashboard](https://resend.com/api-keys) (optional).
25
30
 
26
31
  ## Usage
27
32
 
28
33
  ```ts
34
+ // Import individual tools
29
35
  import { generateText, stepCountIs } from "ai";
30
- import { listSites, listPages, publishSite } from "@224industries/webflow-ai-sdk";
36
+ import { listSites, listPages, updatePage, publishSite } from "@224industries/webflow-ai-sdk/tools";
31
37
 
32
38
  const { text } = await generateText({
33
39
  model: 'openai/gpt-5.2',
34
- tools: { listSites, listPages, publishSite },
40
+ tools: { listSites, listPages, updatePage, publishSite },
35
41
  prompt: "List all my sites and their pages",
36
42
  stopWhen: stepCountIs(5),
37
43
  });
38
44
  ```
39
45
 
46
+ ```ts
47
+ // Or use a pre-configured agent
48
+ import { LeadResponseAgent } from "@224industries/webflow-ai-sdk/agents";
49
+ import { anthropic } from "@ai-sdk/anthropic";
50
+
51
+ const agent = new LeadResponseAgent({
52
+ model: anthropic("claude-sonnet-4-20250514"),
53
+ });
54
+
55
+ const { text } = await agent.generate({
56
+ prompt: "Check my Webflow site for new form submissions and respond to any new leads using the New Lead template in Resend.",
57
+ });
58
+ ```
59
+
40
60
  ## Available Tools
41
61
 
42
62
  | Tool | Description |
@@ -44,11 +64,18 @@ const { text } = await generateText({
44
64
  | `listSites` | List all Webflow sites accessible with the current API token |
45
65
  | `publishSite` | Publish a site to custom domains or the Webflow subdomain |
46
66
  | `listPages` | List all pages for a site with pagination |
67
+ | `updatePage` | Update a page's title, slug, SEO, and Open Graph metadata |
47
68
  | `listForms` | List all forms for a site with field definitions |
48
69
  | `listFormSubmissions` | Retrieve submitted form data, optionally filtered by form |
49
70
  | `listCustomCode` | List all custom code scripts applied to a site and its pages |
50
71
  | `addCustomCode` | Register and apply an inline script to a site or page |
51
72
 
73
+ ## Available Agents
74
+
75
+ | Agent | Description |
76
+ |-------|-------------|
77
+ | `LeadResponseAgent` | Processes Webflow form submissions, creates Resend contacts, and sends template-based response emails |
78
+
52
79
  ## AI SDK Library
53
80
 
54
81
  Find other AI SDK agents and tools in the [AI SDK Library](https://aisdklibrary.com).
@@ -57,6 +84,8 @@ Find other AI SDK agents and tools in the [AI SDK Library](https://aisdklibrary.
57
84
 
58
85
  - [Vercel AI SDK documentation](https://ai-sdk.dev/docs/introduction)
59
86
  - [Webflow API documentation](https://developers.webflow.com)
87
+ - [Resend AI SDK tools](https://github.com/Flash-Brew-Digital/resend-ai-sdk)
88
+ - [Resend API documentation](https://resend.com/docs/api-reference/introduction)
60
89
 
61
90
  ## Contributing
62
91
 
@@ -0,0 +1,166 @@
1
+ import * as ai from 'ai';
2
+ import { ToolLoopAgent, ToolLoopAgentSettings, LanguageModel } from 'ai';
3
+
4
+ declare const tools: {
5
+ listSites: ai.Tool<Record<string, never>, {
6
+ sites: {
7
+ id: string;
8
+ displayName: string;
9
+ shortName: string;
10
+ designerUrl: string;
11
+ settingsUrl: string;
12
+ lastPublished?: string | undefined;
13
+ lastUpdated?: string | undefined;
14
+ previewUrl?: string | undefined;
15
+ timeZone?: string | undefined;
16
+ customDomains?: {
17
+ id: string;
18
+ url: string;
19
+ }[] | undefined;
20
+ }[];
21
+ count: number;
22
+ error?: string | undefined;
23
+ }>;
24
+ listForms: ai.Tool<{
25
+ siteId?: string | undefined;
26
+ limit?: number | undefined;
27
+ offset?: number | undefined;
28
+ }, {
29
+ forms: {
30
+ id: string;
31
+ displayName: string;
32
+ pageId?: string | undefined;
33
+ pageName?: string | undefined;
34
+ formElementId?: string | undefined;
35
+ fields?: Record<string, {
36
+ displayName?: string | undefined;
37
+ type?: string | undefined;
38
+ }> | undefined;
39
+ createdOn?: string | undefined;
40
+ lastUpdated?: string | undefined;
41
+ }[];
42
+ count: number;
43
+ pagination?: {
44
+ limit: number;
45
+ offset: number;
46
+ total: number;
47
+ } | undefined;
48
+ error?: string | undefined;
49
+ }>;
50
+ listFormSubmissions: ai.Tool<{
51
+ siteId?: string | undefined;
52
+ elementId?: string | undefined;
53
+ limit?: number | undefined;
54
+ offset?: number | undefined;
55
+ }, {
56
+ formSubmissions: {
57
+ id: string;
58
+ displayName?: string | undefined;
59
+ dateSubmitted?: string | undefined;
60
+ formResponse?: Record<string, unknown> | undefined;
61
+ }[];
62
+ count: number;
63
+ pagination?: {
64
+ limit: number;
65
+ offset: number;
66
+ total: number;
67
+ } | undefined;
68
+ error?: string | undefined;
69
+ }>;
70
+ sendEmail: ai.Tool<{
71
+ from: string;
72
+ to: string[];
73
+ subject: string;
74
+ html?: string | undefined;
75
+ text?: string | undefined;
76
+ replyTo?: string | string[] | undefined;
77
+ cc?: string[] | undefined;
78
+ bcc?: string[] | undefined;
79
+ scheduledAt?: string | undefined;
80
+ tags?: {
81
+ name: string;
82
+ value: string;
83
+ }[] | undefined;
84
+ attachments?: {
85
+ filename?: string | undefined;
86
+ content?: string | undefined;
87
+ path?: string | undefined;
88
+ contentType?: string | undefined;
89
+ }[] | undefined;
90
+ template?: {
91
+ id: string;
92
+ variables?: Record<string, string | number> | undefined;
93
+ } | undefined;
94
+ topicId?: string | undefined;
95
+ }, {
96
+ success: boolean;
97
+ id: string;
98
+ error?: string | undefined;
99
+ }>;
100
+ createContact: ai.Tool<{
101
+ email: string;
102
+ firstName?: string | undefined;
103
+ lastName?: string | undefined;
104
+ unsubscribed?: boolean | undefined;
105
+ properties?: Record<string, string> | undefined;
106
+ segments?: string[] | undefined;
107
+ topics?: {
108
+ id: string;
109
+ subscription: "opt_in" | "opt_out";
110
+ }[] | undefined;
111
+ }, {
112
+ success: boolean;
113
+ id: string;
114
+ error?: string | undefined;
115
+ }>;
116
+ listTemplates: ai.Tool<{
117
+ limit?: number | undefined;
118
+ after?: string | undefined;
119
+ before?: string | undefined;
120
+ }, {
121
+ templates: {
122
+ id: string;
123
+ name: string;
124
+ status: string;
125
+ alias?: string | undefined;
126
+ createdAt?: string | undefined;
127
+ updatedAt?: string | undefined;
128
+ publishedAt?: string | undefined;
129
+ }[];
130
+ count: number;
131
+ hasMore?: boolean | undefined;
132
+ error?: string | undefined;
133
+ }>;
134
+ getTemplate: ai.Tool<{
135
+ id: string;
136
+ }, {
137
+ success: boolean;
138
+ id: string;
139
+ name?: string | undefined;
140
+ alias?: string | undefined;
141
+ status?: string | undefined;
142
+ from?: string | undefined;
143
+ subject?: string | undefined;
144
+ replyTo?: string | undefined;
145
+ variables?: {
146
+ id: string;
147
+ key: string;
148
+ type: string;
149
+ fallbackValue?: string | undefined;
150
+ }[] | undefined;
151
+ createdAt?: string | undefined;
152
+ updatedAt?: string | undefined;
153
+ publishedAt?: string | undefined;
154
+ error?: string | undefined;
155
+ }>;
156
+ };
157
+ type AgentTools = typeof tools;
158
+ interface LeadResponseAgentOptions extends Omit<ToolLoopAgentSettings<never, AgentTools>, "tools" | "model"> {
159
+ model: LanguageModel;
160
+ instructions?: string;
161
+ }
162
+ declare class LeadResponseAgent extends ToolLoopAgent<never, AgentTools> {
163
+ constructor({ model, instructions, ...rest }: LeadResponseAgentOptions);
164
+ }
165
+
166
+ export { LeadResponseAgent };
@@ -0,0 +1,46 @@
1
+ import {
2
+ listFormSubmissions,
3
+ listForms,
4
+ listSites
5
+ } from "../chunk-2IBR5ZJ2.js";
6
+
7
+ // src/agents/index.ts
8
+ import { ToolLoopAgent } from "ai";
9
+ import {
10
+ createContact,
11
+ getTemplate,
12
+ listTemplates,
13
+ sendEmail
14
+ } from "resend-ai-sdk";
15
+ var defaultInstructions = `You are a lead response agent that automates responding to new form submissions from Webflow sites.
16
+
17
+ Your workflow:
18
+ 1. Use listSites to find available Webflow sites, then listForms to discover forms on those sites.
19
+ 2. Use listFormSubmissions to retrieve recent form submissions and extract contact information (name, email, etc.) from the formResponse data.
20
+ 3. When a submission contains contact information, use createContact to add the person to Resend.
21
+ 4. Use listTemplates to browse available email templates, then getTemplate to inspect a specific template's variables and defaults.
22
+ 5. Use sendEmail with the appropriate template to send a response email to the lead. Fill in any template variables using data from the form submission.
23
+
24
+ The user prompt will specify which site to check, which form to pull submissions from, and which email template to use for the response.`;
25
+ var tools = {
26
+ listSites,
27
+ listForms,
28
+ listFormSubmissions,
29
+ sendEmail,
30
+ createContact,
31
+ listTemplates,
32
+ getTemplate
33
+ };
34
+ var LeadResponseAgent = class extends ToolLoopAgent {
35
+ constructor({ model, instructions, ...rest }) {
36
+ super({
37
+ ...rest,
38
+ model,
39
+ tools,
40
+ instructions: instructions ?? defaultInstructions
41
+ });
42
+ }
43
+ };
44
+ export {
45
+ LeadResponseAgent
46
+ };
@@ -1,6 +1,8 @@
1
- // src/index.ts
1
+ // src/tools/index.ts
2
2
  import { tool } from "ai";
3
- import { z } from "zod";
3
+ import { z as z2 } from "zod";
4
+
5
+ // src/lib/api.ts
4
6
  var BASE_URL = "https://api.webflow.com/v2";
5
7
  var getApiKey = () => {
6
8
  const apiKey = process.env.WEBFLOW_API_KEY;
@@ -36,38 +38,9 @@ var callApi = async (path, options = {}) => {
36
38
  }
37
39
  return response.json();
38
40
  };
39
- var getDefaultSiteId = () => process.env.WEBFLOW_SITE_ID ?? "";
40
- var resolveSiteId = (siteId) => {
41
- const resolved = siteId || getDefaultSiteId();
42
- if (!resolved) {
43
- throw new Error(
44
- "A site ID is required. Either pass a siteId or set the WEBFLOW_SITE_ID environment variable."
45
- );
46
- }
47
- return resolved;
48
- };
49
- var getStringField = (obj, snakeCase, camelCase) => {
50
- if (obj[snakeCase]) {
51
- return String(obj[snakeCase]);
52
- }
53
- if (obj[camelCase]) {
54
- return String(obj[camelCase]);
55
- }
56
- return void 0;
57
- };
58
- var parseFormFields = (rawFields) => {
59
- if (!rawFields) {
60
- return void 0;
61
- }
62
- const fields = {};
63
- for (const [key, value] of Object.entries(rawFields)) {
64
- fields[key] = {
65
- displayName: value.displayName ? String(value.displayName) : void 0,
66
- type: value.type ? String(value.type) : void 0
67
- };
68
- }
69
- return Object.keys(fields).length > 0 ? fields : void 0;
70
- };
41
+
42
+ // src/lib/schemas.ts
43
+ import { z } from "zod";
71
44
  var SiteInfoSchema = z.object({
72
45
  id: z.string().describe("Unique identifier for the Site"),
73
46
  displayName: z.string().describe("Name given to the Site"),
@@ -76,6 +49,8 @@ var SiteInfoSchema = z.object({
76
49
  lastUpdated: z.string().optional().describe("ISO timestamp when the site was last updated"),
77
50
  previewUrl: z.string().optional().describe("URL of the site preview image"),
78
51
  timeZone: z.string().optional().describe("Site timezone"),
52
+ designerUrl: z.string().describe("Direct link to the Webflow Designer for this site"),
53
+ settingsUrl: z.string().describe("Direct link to the site settings in the Webflow Dashboard"),
79
54
  customDomains: z.array(
80
55
  z.object({
81
56
  id: z.string().describe("Domain ID"),
@@ -110,7 +85,11 @@ var PageInfoSchema = z.object({
110
85
  seo: z.object({
111
86
  title: z.string().optional().describe("SEO title"),
112
87
  description: z.string().optional().describe("SEO description")
113
- }).optional().describe("SEO metadata for the page")
88
+ }).optional().describe("SEO metadata for the page"),
89
+ openGraph: z.object({
90
+ title: z.string().optional().describe("Open Graph title"),
91
+ description: z.string().optional().describe("Open Graph description")
92
+ }).optional().describe("Open Graph metadata for social sharing")
114
93
  });
115
94
  var ListPagesResultSchema = z.object({
116
95
  pages: z.array(PageInfoSchema).describe("Array of page metadata"),
@@ -122,6 +101,11 @@ var ListPagesResultSchema = z.object({
122
101
  }).optional().describe("Pagination info"),
123
102
  error: z.string().optional().describe("Error message if failed")
124
103
  });
104
+ var UpdatePageResultSchema = z.object({
105
+ success: z.boolean().describe("Whether the page was updated successfully"),
106
+ page: PageInfoSchema.optional().describe("Updated page data"),
107
+ error: z.string().optional().describe("Error message if failed")
108
+ });
125
109
  var FormInfoSchema = z.object({
126
110
  id: z.string().describe("Unique ID for the Form"),
127
111
  displayName: z.string().describe("Form name displayed on the site"),
@@ -196,10 +180,55 @@ var AddCustomCodeResultSchema = z.object({
196
180
  appliedTo: z.string().optional().describe("Whether the script was applied to a site or page"),
197
181
  error: z.string().optional().describe("Error message if failed")
198
182
  });
183
+
184
+ // src/lib/utils.ts
185
+ var getDefaultSiteId = () => process.env.WEBFLOW_SITE_ID ?? "";
186
+ var resolveSiteId = (siteId) => {
187
+ const resolved = siteId || getDefaultSiteId();
188
+ if (!resolved) {
189
+ throw new Error(
190
+ "A site ID is required. Either pass a siteId or set the WEBFLOW_SITE_ID environment variable."
191
+ );
192
+ }
193
+ return resolved;
194
+ };
195
+ var getStringField = (obj, snakeCase, camelCase) => {
196
+ if (obj[snakeCase]) {
197
+ return String(obj[snakeCase]);
198
+ }
199
+ if (obj[camelCase]) {
200
+ return String(obj[camelCase]);
201
+ }
202
+ return void 0;
203
+ };
204
+ var parseTitleDescription = (raw) => {
205
+ if (!raw) {
206
+ return void 0;
207
+ }
208
+ return {
209
+ title: raw.title ? String(raw.title) : void 0,
210
+ description: raw.description ? String(raw.description) : void 0
211
+ };
212
+ };
213
+ var parseFormFields = (rawFields) => {
214
+ if (!rawFields) {
215
+ return void 0;
216
+ }
217
+ const fields = {};
218
+ for (const [key, value] of Object.entries(rawFields)) {
219
+ fields[key] = {
220
+ displayName: value.displayName ? String(value.displayName) : void 0,
221
+ type: value.type ? String(value.type) : void 0
222
+ };
223
+ }
224
+ return Object.keys(fields).length > 0 ? fields : void 0;
225
+ };
199
226
  var siteIdDescription = getDefaultSiteId() ? ` If not provided, defaults to the configured site: ${getDefaultSiteId()}.` : "";
227
+
228
+ // src/tools/index.ts
200
229
  var listSites = tool({
201
230
  description: "List all Webflow sites that the user currently has access to. Use this tool to discover available sites, find site IDs, check custom domains, or see when a site was last published.",
202
- inputSchema: z.object({}),
231
+ inputSchema: z2.object({}),
203
232
  inputExamples: [{ input: {} }],
204
233
  outputSchema: ListSitesResultSchema,
205
234
  strict: true,
@@ -238,14 +267,14 @@ var listSites = tool({
238
267
  });
239
268
  var publishSite = tool({
240
269
  description: "Publish a Webflow site to one or more domains. Use this tool when the user wants to deploy or publish their site. You must set publishToWebflowSubdomain to true, pass specific custom domain IDs from listSites, or both. Do NOT pass customDomains unless you have retrieved actual domain IDs from listSites \u2014 not all sites have custom domains. Rate limited to 1 publish per minute." + siteIdDescription,
241
- inputSchema: z.object({
242
- siteId: z.string().optional().describe(
243
- `The ID of the site to publish.${siteIdDescription || " Use listSites to find available site IDs."}`
270
+ inputSchema: z2.object({
271
+ siteId: z2.string().optional().describe(
272
+ `The ID of the site to publish.${siteIdDescription || " Use listSites to find available site IDs. Do NOT guess or fabricate site IDs."}`
244
273
  ),
245
- customDomains: z.array(z.string()).optional().describe(
274
+ customDomains: z2.array(z2.string()).optional().describe(
246
275
  "Array of custom domain IDs to publish to. Only provide this if you have retrieved actual domain IDs from the listSites tool. Do NOT guess or fabricate domain IDs. Omit this field entirely if the site has no custom domains."
247
276
  ),
248
- publishToWebflowSubdomain: z.boolean().optional().describe(
277
+ publishToWebflowSubdomain: z2.boolean().optional().describe(
249
278
  "Whether to publish to the default Webflow subdomain (yoursite.webflow.io). Set to true if no custom domains are being used."
250
279
  )
251
280
  }),
@@ -298,12 +327,12 @@ var publishSite = tool({
298
327
  });
299
328
  var listPages = tool({
300
329
  description: "List all pages for a Webflow site. Use this tool to browse site pages, find page IDs for custom code injection, or check page SEO metadata. Supports pagination via limit and offset parameters." + siteIdDescription,
301
- inputSchema: z.object({
302
- siteId: z.string().optional().describe(
303
- `The ID of the site.${siteIdDescription || " Use listSites to find available site IDs."}`
330
+ inputSchema: z2.object({
331
+ siteId: z2.string().optional().describe(
332
+ `The ID of the site.${siteIdDescription || " Use listSites to find available site IDs. Do NOT guess or fabricate site IDs."}`
304
333
  ),
305
- limit: z.number().min(1).max(100).optional().describe("Maximum number of pages to return. Default 100, max 100."),
306
- offset: z.number().optional().describe("Offset for pagination if results exceed the limit.")
334
+ limit: z2.number().min(1).max(100).optional().describe("Maximum number of pages to return. Default 100, max 100."),
335
+ offset: z2.number().optional().describe("Offset for pagination if results exceed the limit.")
307
336
  }),
308
337
  inputExamples: [
309
338
  { input: { siteId: "580e63e98c9a982ac9b8b741" } },
@@ -318,23 +347,22 @@ var listPages = tool({
318
347
  params: { limit, offset }
319
348
  });
320
349
  const rawPages = response.pages ?? [];
321
- const pages = rawPages.map((page) => {
322
- const seo = page.seo;
323
- return {
324
- id: String(page.id ?? ""),
325
- title: String(page.title ?? ""),
326
- slug: String(page.slug ?? ""),
327
- archived: page.archived ? Boolean(page.archived) : void 0,
328
- draft: page.draft ? Boolean(page.draft) : void 0,
329
- createdOn: getStringField(page, "created_on", "createdOn"),
330
- lastUpdated: getStringField(page, "last_updated", "lastUpdated"),
331
- publishedPath: page.publishedPath ? String(page.publishedPath) : void 0,
332
- seo: seo ? {
333
- title: seo.title ? String(seo.title) : void 0,
334
- description: seo.description ? String(seo.description) : void 0
335
- } : void 0
336
- };
337
- });
350
+ const pages = rawPages.map((page) => ({
351
+ id: String(page.id ?? ""),
352
+ title: String(page.title ?? ""),
353
+ slug: String(page.slug ?? ""),
354
+ archived: typeof page.archived === "boolean" ? page.archived : void 0,
355
+ draft: typeof page.draft === "boolean" ? page.draft : void 0,
356
+ createdOn: getStringField(page, "created_on", "createdOn"),
357
+ lastUpdated: getStringField(page, "last_updated", "lastUpdated"),
358
+ publishedPath: page.publishedPath ? String(page.publishedPath) : void 0,
359
+ seo: parseTitleDescription(
360
+ page.seo
361
+ ),
362
+ openGraph: parseTitleDescription(
363
+ page.openGraph
364
+ )
365
+ }));
338
366
  const pagination = response.pagination;
339
367
  return {
340
368
  pages,
@@ -355,14 +383,98 @@ var listPages = tool({
355
383
  }
356
384
  }
357
385
  });
386
+ var updatePage = tool({
387
+ description: "Update the settings of a Webflow page, including its title, slug, SEO metadata, and Open Graph metadata. Use this tool when the user wants to change a page's title, URL slug, SEO title/description, or social sharing metadata. Only the fields you provide will be updated; omitted fields remain unchanged. You must retrieve the page ID from listPages first \u2014 do NOT guess or fabricate page IDs.",
388
+ inputSchema: z2.object({
389
+ pageId: z2.string().describe(
390
+ "The ID of the page to update. Use listPages to find available page IDs. Do NOT guess or fabricate page IDs."
391
+ ),
392
+ title: z2.string().optional().describe("New title for the page."),
393
+ slug: z2.string().optional().describe("New URL slug for the page."),
394
+ seo: z2.object({
395
+ title: z2.string().optional().describe("SEO title for the page."),
396
+ description: z2.string().optional().describe("SEO meta description for the page.")
397
+ }).optional().describe("SEO metadata to update."),
398
+ openGraph: z2.object({
399
+ title: z2.string().optional().describe("Open Graph title for social sharing."),
400
+ description: z2.string().optional().describe("Open Graph description for social sharing.")
401
+ }).optional().describe("Open Graph metadata to update.")
402
+ }),
403
+ inputExamples: [
404
+ {
405
+ input: {
406
+ pageId: "63c720f9347c2139b248e552",
407
+ title: "About Us",
408
+ slug: "about-us"
409
+ }
410
+ },
411
+ {
412
+ input: {
413
+ pageId: "63c720f9347c2139b248e552",
414
+ seo: { title: "About Our Company", description: "Learn more about us" },
415
+ openGraph: { title: "About Us", description: "Our story" }
416
+ }
417
+ }
418
+ ],
419
+ outputSchema: UpdatePageResultSchema,
420
+ strict: true,
421
+ needsApproval: true,
422
+ execute: async ({ pageId, title, slug, seo, openGraph }) => {
423
+ try {
424
+ const body = {};
425
+ if (title !== void 0) {
426
+ body.title = title;
427
+ }
428
+ if (slug !== void 0) {
429
+ body.slug = slug;
430
+ }
431
+ if (seo !== void 0) {
432
+ body.seo = seo;
433
+ }
434
+ if (openGraph !== void 0) {
435
+ body.openGraph = openGraph;
436
+ }
437
+ const response = await callApi(`/pages/${pageId}`, {
438
+ method: "PUT",
439
+ body
440
+ });
441
+ const page = response;
442
+ return {
443
+ success: true,
444
+ page: {
445
+ id: String(page.id ?? ""),
446
+ title: String(page.title ?? ""),
447
+ slug: String(page.slug ?? ""),
448
+ archived: typeof page.archived === "boolean" ? page.archived : void 0,
449
+ draft: typeof page.draft === "boolean" ? page.draft : void 0,
450
+ createdOn: getStringField(page, "created_on", "createdOn"),
451
+ lastUpdated: getStringField(page, "last_updated", "lastUpdated"),
452
+ publishedPath: page.publishedPath ? String(page.publishedPath) : void 0,
453
+ seo: parseTitleDescription(
454
+ page.seo
455
+ ),
456
+ openGraph: parseTitleDescription(
457
+ page.openGraph
458
+ )
459
+ }
460
+ };
461
+ } catch (error) {
462
+ console.error("Error updating page:", error);
463
+ return {
464
+ success: false,
465
+ error: error instanceof Error ? error.message : "Failed to update page"
466
+ };
467
+ }
468
+ }
469
+ });
358
470
  var listForms = tool({
359
471
  description: "List all forms for a Webflow site. Use this tool to browse available forms, find form IDs and element IDs, or inspect form field definitions. The formElementId can be used to filter submissions across component instances. Supports pagination via limit and offset parameters." + siteIdDescription,
360
- inputSchema: z.object({
361
- siteId: z.string().optional().describe(
362
- `The ID of the site.${siteIdDescription || " Use listSites to find available site IDs."}`
472
+ inputSchema: z2.object({
473
+ siteId: z2.string().optional().describe(
474
+ `The ID of the site.${siteIdDescription || " Use listSites to find available site IDs. Do NOT guess or fabricate site IDs."}`
363
475
  ),
364
- limit: z.number().min(1).max(100).optional().describe("Maximum number of forms to return. Default 100, max 100."),
365
- offset: z.number().optional().describe("Offset for pagination if results exceed the limit.")
476
+ limit: z2.number().min(1).max(100).optional().describe("Maximum number of forms to return. Default 100, max 100."),
477
+ offset: z2.number().optional().describe("Offset for pagination if results exceed the limit.")
366
478
  }),
367
479
  inputExamples: [
368
480
  { input: { siteId: "580e63e98c9a982ac9b8b741" } },
@@ -411,17 +523,17 @@ var listForms = tool({
411
523
  });
412
524
  var listFormSubmissions = tool({
413
525
  description: "List form submissions for a Webflow site. Use this tool to retrieve submitted form data such as leads, contact requests, or signups. Optionally filter by elementId to get submissions for a specific form across all component instances. Get the elementId from the listForms tool (returned as formElementId). Supports pagination via limit and offset parameters." + siteIdDescription,
414
- inputSchema: z.object({
415
- siteId: z.string().optional().describe(
416
- `The ID of the site.${siteIdDescription || " Use listSites to find available site IDs."}`
526
+ inputSchema: z2.object({
527
+ siteId: z2.string().optional().describe(
528
+ `The ID of the site.${siteIdDescription || " Use listSites to find available site IDs. Do NOT guess or fabricate site IDs."}`
417
529
  ),
418
- elementId: z.string().optional().describe(
419
- "Filter submissions to a specific form by its element ID. Get this from listForms (formElementId field)."
530
+ elementId: z2.string().optional().describe(
531
+ "Filter submissions to a specific form by its element ID. Get this from the listForms tool (formElementId field). Do NOT guess or fabricate element IDs."
420
532
  ),
421
- limit: z.number().min(1).max(100).optional().describe(
533
+ limit: z2.number().min(1).max(100).optional().describe(
422
534
  "Maximum number of submissions to return. Default 100, max 100."
423
535
  ),
424
- offset: z.number().optional().describe("Offset for pagination if results exceed the limit.")
536
+ offset: z2.number().optional().describe("Offset for pagination if results exceed the limit.")
425
537
  }),
426
538
  inputExamples: [
427
539
  { input: { siteId: "580e63e98c9a982ac9b8b741" } },
@@ -475,14 +587,14 @@ var listFormSubmissions = tool({
475
587
  });
476
588
  var listCustomCode = tool({
477
589
  description: "List all custom code scripts applied to a Webflow site and its pages. Use this tool to audit what scripts are currently active, check script versions, or see where scripts are applied (site-level vs page-level). Supports pagination via limit and offset parameters." + siteIdDescription,
478
- inputSchema: z.object({
479
- siteId: z.string().optional().describe(
480
- `The ID of the site.${siteIdDescription || " Use listSites to find available site IDs."}`
590
+ inputSchema: z2.object({
591
+ siteId: z2.string().optional().describe(
592
+ `The ID of the site.${siteIdDescription || " Use listSites to find available site IDs. Do NOT guess or fabricate site IDs."}`
481
593
  ),
482
- limit: z.number().min(1).max(100).optional().describe(
594
+ limit: z2.number().min(1).max(100).optional().describe(
483
595
  "Maximum number of code blocks to return. Default 100, max 100."
484
596
  ),
485
- offset: z.number().optional().describe("Offset for pagination if results exceed the limit.")
597
+ offset: z2.number().optional().describe("Offset for pagination if results exceed the limit.")
486
598
  }),
487
599
  inputExamples: [
488
600
  { input: { siteId: "580e63e98c9a982ac9b8b741" } },
@@ -535,24 +647,24 @@ var listCustomCode = tool({
535
647
  });
536
648
  var addCustomCode = tool({
537
649
  description: "Register and apply an inline script to a Webflow site or a specific page. Use this tool when the user wants to add tracking scripts (e.g. Google Analytics, Meta Pixel), custom JavaScript, chat widgets, or any inline script. This tool handles both registering the script and applying it in a single step. Inline scripts are limited to 2000 characters. The site must be published after adding custom code for changes to take effect." + siteIdDescription,
538
- inputSchema: z.object({
539
- siteId: z.string().optional().describe(
540
- `The ID of the site to register the script on.${siteIdDescription || " Use listSites to find available site IDs."}`
650
+ inputSchema: z2.object({
651
+ siteId: z2.string().optional().describe(
652
+ `The ID of the site to register the script on.${siteIdDescription || " Use listSites to find available site IDs. Do NOT guess or fabricate site IDs."}`
541
653
  ),
542
- target: z.enum(["site", "page"]).describe(
654
+ target: z2.enum(["site", "page"]).describe(
543
655
  'Where to apply the script. Use "site" for site-wide scripts or "page" for a specific page.'
544
656
  ),
545
- pageId: z.string().optional().describe(
546
- 'The ID of the page to apply the script to. Required when target is "page". Use listPages to find page IDs.'
657
+ pageId: z2.string().optional().describe(
658
+ 'The ID of the page to apply the script to. Required when target is "page". Use listPages to find page IDs. Do NOT guess or fabricate page IDs.'
547
659
  ),
548
- sourceCode: z.string().describe("The JavaScript source code to add. Maximum 2000 characters."),
549
- displayName: z.string().describe(
660
+ sourceCode: z2.string().describe("The JavaScript source code to add. Maximum 2000 characters."),
661
+ displayName: z2.string().describe(
550
662
  "A user-facing name for the script. Must be between 1 and 50 alphanumeric characters (e.g. 'Google Analytics', 'Chat Widget')."
551
663
  ),
552
- version: z.string().describe(
664
+ version: z2.string().describe(
553
665
  'A Semantic Version string for the script (e.g. "1.0.0", "0.0.1").'
554
666
  ),
555
- location: z.enum(["header", "footer"]).describe(
667
+ location: z2.enum(["header", "footer"]).describe(
556
668
  'Where to place the script on the page. Use "header" for scripts that need to load early (e.g. analytics) or "footer" for scripts that can load after content.'
557
669
  )
558
670
  }),
@@ -651,12 +763,14 @@ var addCustomCode = tool({
651
763
  }
652
764
  }
653
765
  });
766
+
654
767
  export {
655
- addCustomCode,
656
- listCustomCode,
657
- listFormSubmissions,
658
- listForms,
659
- listPages,
660
768
  listSites,
661
- publishSite
769
+ publishSite,
770
+ listPages,
771
+ updatePage,
772
+ listForms,
773
+ listFormSubmissions,
774
+ listCustomCode,
775
+ addCustomCode
662
776
  };
@@ -5,6 +5,8 @@ declare const listSites: ai.Tool<Record<string, never>, {
5
5
  id: string;
6
6
  displayName: string;
7
7
  shortName: string;
8
+ designerUrl: string;
9
+ settingsUrl: string;
8
10
  lastPublished?: string | undefined;
9
11
  lastUpdated?: string | undefined;
10
12
  previewUrl?: string | undefined;
@@ -47,6 +49,10 @@ declare const listPages: ai.Tool<{
47
49
  title?: string | undefined;
48
50
  description?: string | undefined;
49
51
  } | undefined;
52
+ openGraph?: {
53
+ title?: string | undefined;
54
+ description?: string | undefined;
55
+ } | undefined;
50
56
  }[];
51
57
  count: number;
52
58
  pagination?: {
@@ -56,6 +62,40 @@ declare const listPages: ai.Tool<{
56
62
  } | undefined;
57
63
  error?: string | undefined;
58
64
  }>;
65
+ declare const updatePage: ai.Tool<{
66
+ pageId: string;
67
+ title?: string | undefined;
68
+ slug?: string | undefined;
69
+ seo?: {
70
+ title?: string | undefined;
71
+ description?: string | undefined;
72
+ } | undefined;
73
+ openGraph?: {
74
+ title?: string | undefined;
75
+ description?: string | undefined;
76
+ } | undefined;
77
+ }, {
78
+ success: boolean;
79
+ page?: {
80
+ id: string;
81
+ title: string;
82
+ slug: string;
83
+ archived?: boolean | undefined;
84
+ draft?: boolean | undefined;
85
+ createdOn?: string | undefined;
86
+ lastUpdated?: string | undefined;
87
+ publishedPath?: string | undefined;
88
+ seo?: {
89
+ title?: string | undefined;
90
+ description?: string | undefined;
91
+ } | undefined;
92
+ openGraph?: {
93
+ title?: string | undefined;
94
+ description?: string | undefined;
95
+ } | undefined;
96
+ } | undefined;
97
+ error?: string | undefined;
98
+ }>;
59
99
  declare const listForms: ai.Tool<{
60
100
  siteId?: string | undefined;
61
101
  limit?: number | undefined;
@@ -128,7 +168,7 @@ declare const listCustomCode: ai.Tool<{
128
168
  error?: string | undefined;
129
169
  }>;
130
170
  declare const addCustomCode: ai.Tool<{
131
- target: "site" | "page";
171
+ target: "page" | "site";
132
172
  sourceCode: string;
133
173
  displayName: string;
134
174
  version: string;
@@ -142,4 +182,4 @@ declare const addCustomCode: ai.Tool<{
142
182
  error?: string | undefined;
143
183
  }>;
144
184
 
145
- export { addCustomCode, listCustomCode, listFormSubmissions, listForms, listPages, listSites, publishSite };
185
+ export { addCustomCode, listCustomCode, listFormSubmissions, listForms, listPages, listSites, publishSite, updatePage };
@@ -0,0 +1,20 @@
1
+ import {
2
+ addCustomCode,
3
+ listCustomCode,
4
+ listFormSubmissions,
5
+ listForms,
6
+ listPages,
7
+ listSites,
8
+ publishSite,
9
+ updatePage
10
+ } from "../chunk-2IBR5ZJ2.js";
11
+ export {
12
+ addCustomCode,
13
+ listCustomCode,
14
+ listFormSubmissions,
15
+ listForms,
16
+ listPages,
17
+ listSites,
18
+ publishSite,
19
+ updatePage
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@224industries/webflow-ai-sdk",
3
- "version": "0.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Webflow tools for the AI SDK",
5
5
  "keywords": [
6
6
  "ai",
@@ -22,13 +22,14 @@
22
22
  "type": "git",
23
23
  "url": "git+https://github.com/224-Industries/webflow-ai-sdk.git"
24
24
  },
25
- "main": "./dist/index.js",
26
- "module": "./dist/index.js",
27
- "types": "./dist/index.d.ts",
28
25
  "exports": {
29
- ".": {
30
- "import": "./dist/index.js",
31
- "types": "./dist/index.d.ts"
26
+ "./tools": {
27
+ "import": "./dist/tools/index.js",
28
+ "types": "./dist/tools/index.d.ts"
29
+ },
30
+ "./agents": {
31
+ "import": "./dist/agents/index.js",
32
+ "types": "./dist/agents/index.d.ts"
32
33
  }
33
34
  },
34
35
  "files": [
@@ -37,7 +38,7 @@
37
38
  ],
38
39
  "type": "module",
39
40
  "scripts": {
40
- "build": "tsup src/index.ts --format esm --dts",
41
+ "build": "tsup src/tools/index.ts src/agents/index.ts --format esm --dts",
41
42
  "test": "vitest run",
42
43
  "prepublishOnly": "pnpm build",
43
44
  "check": "ultracite check",
@@ -51,11 +52,12 @@
51
52
  "tsup": "^8.5.1",
52
53
  "tsx": "^4.21.0",
53
54
  "typescript": "^5.9.3",
54
- "ultracite": "7.1.5",
55
+ "ultracite": "7.2.1",
55
56
  "vitest": "^4.0.18"
56
57
  },
57
58
  "peerDependencies": {
58
59
  "ai": "^6.0.67",
60
+ "resend-ai-sdk": "^1.0.0",
59
61
  "zod": "^4.3.6"
60
62
  },
61
63
  "engines": {