me3-protocol 2.1.0 → 2.2.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/index.d.ts CHANGED
@@ -47,6 +47,48 @@ export interface Me3Footer {
47
47
  /** Optional custom footer link */
48
48
  link?: Me3FooterLink;
49
49
  }
50
+ /**
51
+ * Newsletter subscription intent.
52
+ * When enabled, the site accepts email subscriptions via POST /api/subscribe
53
+ */
54
+ export interface Me3IntentSubscribe {
55
+ /** Whether newsletter signups are enabled */
56
+ enabled: boolean;
57
+ /** Newsletter title (e.g., "AI Weekly") - for agents to present context */
58
+ title?: string;
59
+ /** What subscribers will receive - for agents to explain the value */
60
+ description?: string;
61
+ /** How often subscribers will hear from you */
62
+ frequency?: "daily" | "weekly" | "monthly" | "irregular";
63
+ }
64
+ /**
65
+ * Booking/scheduling intent.
66
+ * Declares that the person accepts meeting bookings.
67
+ */
68
+ export interface Me3IntentBook {
69
+ /** Whether booking is enabled */
70
+ enabled: boolean;
71
+ /** Meeting title (e.g., "30-min Consultation") */
72
+ title?: string;
73
+ /** What the meeting is about */
74
+ description?: string;
75
+ /** Meeting duration in minutes */
76
+ duration?: number;
77
+ /** Booking provider (e.g., "cal.com", "calendly") */
78
+ provider?: string;
79
+ /** Direct booking URL */
80
+ url: string;
81
+ }
82
+ /**
83
+ * Intents object - declares what actions visitors/agents can take.
84
+ * This is the machine-readable API contract for interacting with a person.
85
+ */
86
+ export interface Me3Intents {
87
+ /** Newsletter subscription */
88
+ subscribe?: Me3IntentSubscribe;
89
+ /** Meeting booking */
90
+ book?: Me3IntentBook;
91
+ }
50
92
  export interface Me3Profile {
51
93
  /** Protocol version */
52
94
  version: string;
@@ -74,6 +116,11 @@ export interface Me3Profile {
74
116
  * - `false`: hide footer (renderer may restrict this to Pro tiers)
75
117
  */
76
118
  footer?: Me3Footer | false;
119
+ /**
120
+ * Intents - machine-readable actions that visitors/agents can take.
121
+ * This is the API contract for interacting with the person.
122
+ */
123
+ intents?: Me3Intents;
77
124
  }
78
125
  export interface ValidationError {
79
126
  field: string;
package/dist/index.js CHANGED
@@ -21,6 +21,9 @@ const VALID_BUTTON_STYLES = ["primary", "secondary", "outline"];
21
21
  const URL_REGEX = /^https?:\/\/.+/i;
22
22
  const MAX_FOOTER_TEXT_LENGTH = 200;
23
23
  const MAX_FOOTER_LINK_TEXT_LENGTH = 60;
24
+ const MAX_INTENT_TITLE_LENGTH = 100;
25
+ const MAX_INTENT_DESCRIPTION_LENGTH = 300;
26
+ const VALID_FREQUENCIES = ["daily", "weekly", "monthly", "irregular"];
24
27
  /**
25
28
  * Validate a me3 profile object
26
29
  */
@@ -184,7 +187,10 @@ function validateProfile(data) {
184
187
  const footer = profile.footer;
185
188
  if (footer.text !== undefined) {
186
189
  if (typeof footer.text !== "string") {
187
- errors.push({ field: "footer.text", message: "Footer text must be a string" });
190
+ errors.push({
191
+ field: "footer.text",
192
+ message: "Footer text must be a string",
193
+ });
188
194
  }
189
195
  else if (footer.text.length > MAX_FOOTER_TEXT_LENGTH) {
190
196
  errors.push({
@@ -195,12 +201,18 @@ function validateProfile(data) {
195
201
  }
196
202
  if (footer.link !== undefined) {
197
203
  if (typeof footer.link !== "object" || footer.link === null) {
198
- errors.push({ field: "footer.link", message: "Footer link must be an object" });
204
+ errors.push({
205
+ field: "footer.link",
206
+ message: "Footer link must be an object",
207
+ });
199
208
  }
200
209
  else {
201
210
  const link = footer.link;
202
211
  if (!link.text || typeof link.text !== "string") {
203
- errors.push({ field: "footer.link.text", message: "Footer link text is required" });
212
+ errors.push({
213
+ field: "footer.link.text",
214
+ message: "Footer link text is required",
215
+ });
204
216
  }
205
217
  else if (link.text.length > MAX_FOOTER_LINK_TEXT_LENGTH) {
206
218
  errors.push({
@@ -209,7 +221,10 @@ function validateProfile(data) {
209
221
  });
210
222
  }
211
223
  if (!link.url || typeof link.url !== "string") {
212
- errors.push({ field: "footer.link.url", message: "Footer link URL is required" });
224
+ errors.push({
225
+ field: "footer.link.url",
226
+ message: "Footer link URL is required",
227
+ });
213
228
  }
214
229
  else if (!URL_REGEX.test(link.url)) {
215
230
  errors.push({
@@ -262,6 +277,141 @@ function validateProfile(data) {
262
277
  });
263
278
  }
264
279
  }
280
+ // Intents (optional)
281
+ if (profile.intents !== undefined) {
282
+ if (typeof profile.intents !== "object" || profile.intents === null) {
283
+ errors.push({ field: "intents", message: "Intents must be an object" });
284
+ }
285
+ else {
286
+ const intents = profile.intents;
287
+ // Validate subscribe intent
288
+ if (intents.subscribe !== undefined) {
289
+ if (typeof intents.subscribe !== "object" ||
290
+ intents.subscribe === null) {
291
+ errors.push({
292
+ field: "intents.subscribe",
293
+ message: "Subscribe intent must be an object",
294
+ });
295
+ }
296
+ else {
297
+ const subscribe = intents.subscribe;
298
+ if (typeof subscribe.enabled !== "boolean") {
299
+ errors.push({
300
+ field: "intents.subscribe.enabled",
301
+ message: "Subscribe enabled must be a boolean",
302
+ });
303
+ }
304
+ if (subscribe.title !== undefined) {
305
+ if (typeof subscribe.title !== "string") {
306
+ errors.push({
307
+ field: "intents.subscribe.title",
308
+ message: "Subscribe title must be a string",
309
+ });
310
+ }
311
+ else if (subscribe.title.length > MAX_INTENT_TITLE_LENGTH) {
312
+ errors.push({
313
+ field: "intents.subscribe.title",
314
+ message: `Subscribe title must be ${MAX_INTENT_TITLE_LENGTH} characters or less`,
315
+ });
316
+ }
317
+ }
318
+ if (subscribe.description !== undefined) {
319
+ if (typeof subscribe.description !== "string") {
320
+ errors.push({
321
+ field: "intents.subscribe.description",
322
+ message: "Subscribe description must be a string",
323
+ });
324
+ }
325
+ else if (subscribe.description.length > MAX_INTENT_DESCRIPTION_LENGTH) {
326
+ errors.push({
327
+ field: "intents.subscribe.description",
328
+ message: `Subscribe description must be ${MAX_INTENT_DESCRIPTION_LENGTH} characters or less`,
329
+ });
330
+ }
331
+ }
332
+ if (subscribe.frequency !== undefined &&
333
+ !VALID_FREQUENCIES.includes(subscribe.frequency)) {
334
+ errors.push({
335
+ field: "intents.subscribe.frequency",
336
+ message: `Subscribe frequency must be one of: ${VALID_FREQUENCIES.join(", ")}`,
337
+ });
338
+ }
339
+ }
340
+ }
341
+ // Validate book intent
342
+ if (intents.book !== undefined) {
343
+ if (typeof intents.book !== "object" || intents.book === null) {
344
+ errors.push({
345
+ field: "intents.book",
346
+ message: "Book intent must be an object",
347
+ });
348
+ }
349
+ else {
350
+ const book = intents.book;
351
+ if (typeof book.enabled !== "boolean") {
352
+ errors.push({
353
+ field: "intents.book.enabled",
354
+ message: "Book enabled must be a boolean",
355
+ });
356
+ }
357
+ if (book.title !== undefined) {
358
+ if (typeof book.title !== "string") {
359
+ errors.push({
360
+ field: "intents.book.title",
361
+ message: "Book title must be a string",
362
+ });
363
+ }
364
+ else if (book.title.length > MAX_INTENT_TITLE_LENGTH) {
365
+ errors.push({
366
+ field: "intents.book.title",
367
+ message: `Book title must be ${MAX_INTENT_TITLE_LENGTH} characters or less`,
368
+ });
369
+ }
370
+ }
371
+ if (book.description !== undefined) {
372
+ if (typeof book.description !== "string") {
373
+ errors.push({
374
+ field: "intents.book.description",
375
+ message: "Book description must be a string",
376
+ });
377
+ }
378
+ else if (book.description.length > MAX_INTENT_DESCRIPTION_LENGTH) {
379
+ errors.push({
380
+ field: "intents.book.description",
381
+ message: `Book description must be ${MAX_INTENT_DESCRIPTION_LENGTH} characters or less`,
382
+ });
383
+ }
384
+ }
385
+ if (book.duration !== undefined &&
386
+ typeof book.duration !== "number") {
387
+ errors.push({
388
+ field: "intents.book.duration",
389
+ message: "Book duration must be a number (minutes)",
390
+ });
391
+ }
392
+ if (book.provider !== undefined &&
393
+ typeof book.provider !== "string") {
394
+ errors.push({
395
+ field: "intents.book.provider",
396
+ message: "Book provider must be a string",
397
+ });
398
+ }
399
+ if (!book.url || typeof book.url !== "string") {
400
+ errors.push({
401
+ field: "intents.book.url",
402
+ message: "Book URL is required",
403
+ });
404
+ }
405
+ else if (!URL_REGEX.test(book.url)) {
406
+ errors.push({
407
+ field: "intents.book.url",
408
+ message: "Book URL must be a valid URL starting with http:// or https://",
409
+ });
410
+ }
411
+ }
412
+ }
413
+ }
414
+ }
265
415
  if (errors.length > 0) {
266
416
  return { valid: false, errors };
267
417
  }
@@ -27,5 +27,21 @@
27
27
  ],
28
28
  "pages": [
29
29
  { "slug": "about", "title": "About", "file": "about.md", "visible": true }
30
- ]
30
+ ],
31
+ "intents": {
32
+ "subscribe": {
33
+ "enabled": true,
34
+ "title": "Weekly Insights",
35
+ "description": "Curated thoughts on design, tech, and creativity every Sunday",
36
+ "frequency": "weekly"
37
+ },
38
+ "book": {
39
+ "enabled": true,
40
+ "title": "30-min Consultation",
41
+ "description": "Let's discuss your project",
42
+ "duration": 30,
43
+ "provider": "cal.com",
44
+ "url": "https://cal.com/example"
45
+ }
46
+ }
31
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "me3-protocol",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/schema.json CHANGED
@@ -65,6 +65,88 @@
65
65
  ],
66
66
  "type": "object"
67
67
  },
68
+ "Me3IntentBook": {
69
+ "additionalProperties": false,
70
+ "description": "Booking/scheduling intent. Declares that the person accepts meeting bookings.",
71
+ "properties": {
72
+ "description": {
73
+ "description": "What the meeting is about",
74
+ "type": "string"
75
+ },
76
+ "duration": {
77
+ "description": "Meeting duration in minutes",
78
+ "type": "number"
79
+ },
80
+ "enabled": {
81
+ "description": "Whether booking is enabled",
82
+ "type": "boolean"
83
+ },
84
+ "provider": {
85
+ "description": "Booking provider (e.g., \"cal.com\", \"calendly\")",
86
+ "type": "string"
87
+ },
88
+ "title": {
89
+ "description": "Meeting title (e.g., \"30-min Consultation\")",
90
+ "type": "string"
91
+ },
92
+ "url": {
93
+ "description": "Direct booking URL",
94
+ "type": "string"
95
+ }
96
+ },
97
+ "required": [
98
+ "enabled",
99
+ "url"
100
+ ],
101
+ "type": "object"
102
+ },
103
+ "Me3IntentSubscribe": {
104
+ "additionalProperties": false,
105
+ "description": "Newsletter subscription intent. When enabled, the site accepts email subscriptions via POST /api/subscribe",
106
+ "properties": {
107
+ "description": {
108
+ "description": "What subscribers will receive - for agents to explain the value",
109
+ "type": "string"
110
+ },
111
+ "enabled": {
112
+ "description": "Whether newsletter signups are enabled",
113
+ "type": "boolean"
114
+ },
115
+ "frequency": {
116
+ "description": "How often subscribers will hear from you",
117
+ "enum": [
118
+ "daily",
119
+ "weekly",
120
+ "monthly",
121
+ "irregular"
122
+ ],
123
+ "type": "string"
124
+ },
125
+ "title": {
126
+ "description": "Newsletter title (e.g., \"AI Weekly\") - for agents to present context",
127
+ "type": "string"
128
+ }
129
+ },
130
+ "required": [
131
+ "enabled"
132
+ ],
133
+ "type": "object"
134
+ },
135
+ "Me3Intents": {
136
+ "additionalProperties": false,
137
+ "description": "Intents object - declares what actions visitors/agents can take. This is the machine-readable API contract for interacting with a person.",
138
+ "properties": {
139
+ "book": {
140
+ "$ref": "#/definitions/Me3IntentBook",
141
+ "description": "Meeting booking"
142
+ },
143
+ "subscribe": {
144
+ "$ref": "#/definitions/Me3IntentSubscribe",
145
+ "description": "Newsletter subscription"
146
+ }
147
+ },
148
+ "type": "object"
149
+ },
68
150
  "Me3Links": {
69
151
  "additionalProperties": {
70
152
  "anyOf": [
@@ -171,6 +253,10 @@
171
253
  "description": "Username/handle",
172
254
  "type": "string"
173
255
  },
256
+ "intents": {
257
+ "$ref": "#/definitions/Me3Intents",
258
+ "description": "Intents - machine-readable actions that visitors/agents can take. This is the API contract for interacting with the person."
259
+ },
174
260
  "links": {
175
261
  "$ref": "#/definitions/Me3Links",
176
262
  "description": "Social and external links"