me3-protocol 2.1.0 → 2.3.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 +96 -77
- package/dist/index.d.ts +67 -0
- package/dist/index.js +231 -4
- package/examples/full.json +25 -1
- package/package.json +1 -1
- package/schema.json +153 -0
package/README.md
CHANGED
|
@@ -1,115 +1,126 @@
|
|
|
1
1
|
# me3 Protocol (me.json)
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
**The place machines check before acting on a person.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`me.json` is a minimal protocol that lets you declare what actions AI agents and services can take on your behalf—and how.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## The Problem
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Machines are already making decisions about people:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
- "Should I book a meeting with this person?"
|
|
12
|
+
- "Can I subscribe them to updates?"
|
|
13
|
+
- "How should I introduce them?"
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
Without an authoritative source, they guess. They scrape. They get it wrong.
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
Schema.org describes _pages_. `me.json` declares _people_—their identity, their preferences, and their **intents**.
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
2. **Fallback**: `https://yourdomain.com/.well-known/me`
|
|
19
|
+
## The Solution: Intents
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
The core of `me.json` is the `intents` object—machine-readable declarations of what visitors and agents can do:
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"version": "0.1",
|
|
26
|
+
"name": "Jane Doe",
|
|
27
|
+
"bio": "Creative Director at Studio X",
|
|
28
|
+
"intents": {
|
|
29
|
+
"subscribe": {
|
|
30
|
+
"enabled": true,
|
|
31
|
+
"title": "Design Weekly",
|
|
32
|
+
"description": "Curated design links every Sunday",
|
|
33
|
+
"frequency": "weekly"
|
|
34
|
+
},
|
|
35
|
+
"book": {
|
|
36
|
+
"enabled": true,
|
|
37
|
+
"title": "30-min Consultation",
|
|
38
|
+
"description": "Let's discuss your project",
|
|
39
|
+
"duration": 30,
|
|
40
|
+
"url": "https://cal.com/janedoe"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
**Without `me.json`**: An AI asked "Can I book a call with Jane?" has to guess, scrape her site, or fail.
|
|
30
47
|
|
|
31
|
-
The
|
|
48
|
+
**With `me.json`**: The AI reads `intents.book`, confirms it's enabled, and knows exactly where to send the user.
|
|
32
49
|
|
|
33
|
-
|
|
50
|
+
That's the protocol's value: **authority before action**.
|
|
34
51
|
|
|
35
|
-
|
|
52
|
+
## Supported Intents
|
|
36
53
|
|
|
37
|
-
|
|
54
|
+
| Intent | Purpose | Key Fields |
|
|
55
|
+
| :---------- | :--------------------------- | :--------------------------------------------------- |
|
|
56
|
+
| `subscribe` | Newsletter/updates signup | `enabled`, `title`, `description`, `frequency` |
|
|
57
|
+
| `book` | Meeting/consultation booking | `enabled`, `title`, `description`, `url`, `duration` |
|
|
38
58
|
|
|
39
|
-
|
|
40
|
-
| :--------- | :------- | :------- | :------------------------------------------------ |
|
|
41
|
-
| `version` | `string` | **Yes** | Protocol version (currently "0.1"). |
|
|
42
|
-
| `name` | `string` | **Yes** | Your display name. |
|
|
43
|
-
| `handle` | `string` | No | Your preferred username/handle. |
|
|
44
|
-
| `bio` | `string` | No | Short bio (max 500 chars). |
|
|
45
|
-
| `avatar` | `string` | No | URL to your profile picture. |
|
|
46
|
-
| `banner` | `string` | No | URL to a header/banner image. |
|
|
47
|
-
| `location` | `string` | No | Freeform location string (e.g. "Remote"). |
|
|
48
|
-
| `links` | `object` | No | Social links (website, github, twitter, etc.). |
|
|
49
|
-
| `buttons` | `array` | No | Primary actions (e.g., "Book Call", "Subscribe"). |
|
|
50
|
-
| `pages` | `array` | No | Custom content pages. |
|
|
51
|
-
| `footer` | `object` | No | Custom footer config (or `false` to hide). |
|
|
59
|
+
More intents (like `contact` for routing preferences) are planned.
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
---
|
|
54
62
|
|
|
55
|
-
|
|
63
|
+
## Full Schema
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
Beyond intents, `me.json` includes identity and presentation fields:
|
|
66
|
+
|
|
67
|
+
| Field | Type | Required | Description |
|
|
68
|
+
| :--------- | :------- | :------- | :--------------------------------------------------- |
|
|
69
|
+
| `version` | `string` | **Yes** | Protocol version (currently `"0.1"`). |
|
|
70
|
+
| `name` | `string` | **Yes** | Display name. |
|
|
71
|
+
| `handle` | `string` | No | Preferred username/handle. |
|
|
72
|
+
| `bio` | `string` | No | Short bio (max 500 chars). |
|
|
73
|
+
| `avatar` | `string` | No | Profile picture URL. |
|
|
74
|
+
| `banner` | `string` | No | Header/banner image URL. |
|
|
75
|
+
| `location` | `string` | No | Freeform location (e.g., "Berlin" or "Remote"). |
|
|
76
|
+
| `links` | `object` | No | Social links (`website`, `github`, `twitter`, etc.). |
|
|
77
|
+
| `buttons` | `array` | No | Call-to-action buttons for human visitors. |
|
|
78
|
+
| `pages` | `array` | No | Custom content pages (markdown). |
|
|
79
|
+
| `intents` | `object` | No | Machine-actionable declarations (see above). |
|
|
80
|
+
| `footer` | `object` | No | Footer config (or `false` to hide). |
|
|
81
|
+
|
|
82
|
+
See [`examples/full.json`](./examples/full.json) for a complete example.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Hosting & Discovery
|
|
87
|
+
|
|
88
|
+
Your `me.json` must be publicly accessible at:
|
|
79
89
|
|
|
80
|
-
|
|
90
|
+
1. **Primary**: `https://yourdomain.com/me.json`
|
|
91
|
+
2. **Fallback**: `https://yourdomain.com/.well-known/me`
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
### Requirements
|
|
83
94
|
|
|
84
|
-
- **
|
|
85
|
-
- **
|
|
86
|
-
- **
|
|
87
|
-
- **NOT Reputation / Ranking**: No scoring, ranking, endorsements, verification, or algorithmic ordering.
|
|
95
|
+
- **HTTPS only**
|
|
96
|
+
- **CORS enabled**: Serve with `Access-Control-Allow-Origin: *` so browser-based agents can read it
|
|
97
|
+
- **Content-Type**: `application/json`
|
|
88
98
|
|
|
89
|
-
|
|
99
|
+
---
|
|
90
100
|
|
|
91
|
-
|
|
92
|
-
- **Backwards compatibility**: `0.1` is intended to stay stable. Changes should be additive and conservative.
|
|
93
|
-
- **Extensions**: top-level fields are intentionally strict. If you need custom keys, prefer placing them under `links` (e.g. `"links": { "mastodon": "...", "custom": "..." }`) until the protocol defines a first-class place for extensions.
|
|
101
|
+
## What me.json is NOT
|
|
94
102
|
|
|
95
|
-
|
|
103
|
+
- **NOT authentication** — This is public data. No logins, no private keys.
|
|
104
|
+
- **NOT a social network** — No feeds, no likes, no central server.
|
|
105
|
+
- **NOT a platform** — Host it anywhere: GitHub Pages, Vercel, your own server.
|
|
106
|
+
- **NOT reputation** — No scores, rankings, or verification.
|
|
96
107
|
|
|
97
|
-
|
|
108
|
+
---
|
|
98
109
|
|
|
99
|
-
|
|
110
|
+
## Usage
|
|
111
|
+
|
|
112
|
+
### Install
|
|
100
113
|
|
|
101
114
|
```bash
|
|
102
115
|
npm install me3-protocol
|
|
103
116
|
```
|
|
104
117
|
|
|
118
|
+
### Validate
|
|
119
|
+
|
|
105
120
|
```typescript
|
|
106
121
|
import { validateProfile, parseMe3Json } from "me3-protocol";
|
|
107
122
|
|
|
108
|
-
|
|
109
|
-
const result = validateProfile(myProfileData);
|
|
110
|
-
|
|
111
|
-
// Parse and validate a string
|
|
112
|
-
const result = parseMe3Json(jsonString);
|
|
123
|
+
const result = validateProfile(profileData);
|
|
113
124
|
|
|
114
125
|
if (!result.valid) {
|
|
115
126
|
console.error(result.errors);
|
|
@@ -118,4 +129,12 @@ if (!result.valid) {
|
|
|
118
129
|
|
|
119
130
|
### JSON Schema
|
|
120
131
|
|
|
121
|
-
A standard JSON Schema is available
|
|
132
|
+
A standard JSON Schema is available at [`schema.json`](./schema.json).
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Versioning
|
|
137
|
+
|
|
138
|
+
- **Current version**: `0.1`
|
|
139
|
+
- **Stability**: Additive changes only. Breaking changes require a version bump.
|
|
140
|
+
- **Extensions**: Custom fields should go under `links` until the protocol defines extension points.
|
package/dist/index.d.ts
CHANGED
|
@@ -47,6 +47,68 @@ 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
|
+
* Availability windows for booking.
|
|
66
|
+
* Defines when the person is available for meetings.
|
|
67
|
+
*/
|
|
68
|
+
export interface Me3BookingAvailability {
|
|
69
|
+
/** Timezone for the availability windows (e.g., "America/New_York") */
|
|
70
|
+
timezone: string;
|
|
71
|
+
/** Weekly availability windows by day */
|
|
72
|
+
windows: {
|
|
73
|
+
monday?: string[];
|
|
74
|
+
tuesday?: string[];
|
|
75
|
+
wednesday?: string[];
|
|
76
|
+
thursday?: string[];
|
|
77
|
+
friday?: string[];
|
|
78
|
+
saturday?: string[];
|
|
79
|
+
sunday?: string[];
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Booking/scheduling intent.
|
|
84
|
+
* Declares that the person accepts meeting bookings.
|
|
85
|
+
*/
|
|
86
|
+
export interface Me3IntentBook {
|
|
87
|
+
/** Whether booking is enabled */
|
|
88
|
+
enabled: boolean;
|
|
89
|
+
/** Meeting title (e.g., "30-min Consultation") */
|
|
90
|
+
title?: string;
|
|
91
|
+
/** What the meeting is about */
|
|
92
|
+
description?: string;
|
|
93
|
+
/** Meeting duration in minutes */
|
|
94
|
+
duration?: number;
|
|
95
|
+
/** Booking provider (e.g., "cal.com", "calendly") - for external providers */
|
|
96
|
+
provider?: string;
|
|
97
|
+
/** Direct booking URL - for external booking systems */
|
|
98
|
+
url?: string;
|
|
99
|
+
/** Availability windows - for native me3 booking */
|
|
100
|
+
availability?: Me3BookingAvailability;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Intents object - declares what actions visitors/agents can take.
|
|
104
|
+
* This is the machine-readable API contract for interacting with a person.
|
|
105
|
+
*/
|
|
106
|
+
export interface Me3Intents {
|
|
107
|
+
/** Newsletter subscription */
|
|
108
|
+
subscribe?: Me3IntentSubscribe;
|
|
109
|
+
/** Meeting booking */
|
|
110
|
+
book?: Me3IntentBook;
|
|
111
|
+
}
|
|
50
112
|
export interface Me3Profile {
|
|
51
113
|
/** Protocol version */
|
|
52
114
|
version: string;
|
|
@@ -74,6 +136,11 @@ export interface Me3Profile {
|
|
|
74
136
|
* - `false`: hide footer (renderer may restrict this to Pro tiers)
|
|
75
137
|
*/
|
|
76
138
|
footer?: Me3Footer | false;
|
|
139
|
+
/**
|
|
140
|
+
* Intents - machine-readable actions that visitors/agents can take.
|
|
141
|
+
* This is the API contract for interacting with the person.
|
|
142
|
+
*/
|
|
143
|
+
intents?: Me3Intents;
|
|
77
144
|
}
|
|
78
145
|
export interface ValidationError {
|
|
79
146
|
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({
|
|
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({
|
|
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({
|
|
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({
|
|
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,218 @@ 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
|
+
// URL is optional if availability is set (native me3 booking)
|
|
400
|
+
if (book.url !== undefined) {
|
|
401
|
+
if (typeof book.url !== "string") {
|
|
402
|
+
errors.push({
|
|
403
|
+
field: "intents.book.url",
|
|
404
|
+
message: "Book URL must be a string",
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
else if (!URL_REGEX.test(book.url)) {
|
|
408
|
+
errors.push({
|
|
409
|
+
field: "intents.book.url",
|
|
410
|
+
message: "Book URL must be a valid URL starting with http:// or https://",
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
// Validate availability if present
|
|
415
|
+
if (book.availability !== undefined) {
|
|
416
|
+
if (typeof book.availability !== "object" ||
|
|
417
|
+
book.availability === null) {
|
|
418
|
+
errors.push({
|
|
419
|
+
field: "intents.book.availability",
|
|
420
|
+
message: "Book availability must be an object",
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
const availability = book.availability;
|
|
425
|
+
if (!availability.timezone ||
|
|
426
|
+
typeof availability.timezone !== "string") {
|
|
427
|
+
errors.push({
|
|
428
|
+
field: "intents.book.availability.timezone",
|
|
429
|
+
message: "Availability timezone is required",
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
if (availability.windows !== undefined) {
|
|
433
|
+
if (typeof availability.windows !== "object" ||
|
|
434
|
+
availability.windows === null) {
|
|
435
|
+
errors.push({
|
|
436
|
+
field: "intents.book.availability.windows",
|
|
437
|
+
message: "Availability windows must be an object",
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
const windows = availability.windows;
|
|
442
|
+
const validDays = [
|
|
443
|
+
"monday",
|
|
444
|
+
"tuesday",
|
|
445
|
+
"wednesday",
|
|
446
|
+
"thursday",
|
|
447
|
+
"friday",
|
|
448
|
+
"saturday",
|
|
449
|
+
"sunday",
|
|
450
|
+
];
|
|
451
|
+
const timeWindowRegex = /^\d{2}:\d{2}-\d{2}:\d{2}$/;
|
|
452
|
+
for (const [day, value] of Object.entries(windows)) {
|
|
453
|
+
if (!validDays.includes(day)) {
|
|
454
|
+
errors.push({
|
|
455
|
+
field: `intents.book.availability.windows.${day}`,
|
|
456
|
+
message: `Invalid day: ${day}`,
|
|
457
|
+
});
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
if (!Array.isArray(value)) {
|
|
461
|
+
errors.push({
|
|
462
|
+
field: `intents.book.availability.windows.${day}`,
|
|
463
|
+
message: `Windows for ${day} must be an array`,
|
|
464
|
+
});
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
for (const window of value) {
|
|
468
|
+
if (typeof window !== "string" ||
|
|
469
|
+
!timeWindowRegex.test(window)) {
|
|
470
|
+
errors.push({
|
|
471
|
+
field: `intents.book.availability.windows.${day}`,
|
|
472
|
+
message: `Invalid time window format. Use HH:MM-HH:MM`,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Either URL or availability must be set for booking to work
|
|
482
|
+
if (!book.url && !book.availability) {
|
|
483
|
+
errors.push({
|
|
484
|
+
field: "intents.book",
|
|
485
|
+
message: "Book intent requires either a URL (for external booking) or availability (for native booking)",
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
265
492
|
if (errors.length > 0) {
|
|
266
493
|
return { valid: false, errors };
|
|
267
494
|
}
|
package/examples/full.json
CHANGED
|
@@ -27,5 +27,29 @@
|
|
|
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
|
+
"availability": {
|
|
44
|
+
"timezone": "America/New_York",
|
|
45
|
+
"windows": {
|
|
46
|
+
"monday": ["09:00-12:00", "14:00-17:00"],
|
|
47
|
+
"tuesday": ["09:00-17:00"],
|
|
48
|
+
"wednesday": ["09:00-17:00"],
|
|
49
|
+
"thursday": ["09:00-17:00"],
|
|
50
|
+
"friday": ["09:00-12:00"]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
31
55
|
}
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -2,6 +2,70 @@
|
|
|
2
2
|
"$ref": "#/definitions/Me3Profile",
|
|
3
3
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
4
4
|
"definitions": {
|
|
5
|
+
"Me3BookingAvailability": {
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"description": "Availability windows for booking. Defines when the person is available for meetings.",
|
|
8
|
+
"properties": {
|
|
9
|
+
"timezone": {
|
|
10
|
+
"description": "Timezone for the availability windows (e.g., \"America/New_York\")",
|
|
11
|
+
"type": "string"
|
|
12
|
+
},
|
|
13
|
+
"windows": {
|
|
14
|
+
"additionalProperties": false,
|
|
15
|
+
"description": "Weekly availability windows by day",
|
|
16
|
+
"properties": {
|
|
17
|
+
"friday": {
|
|
18
|
+
"items": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"type": "array"
|
|
22
|
+
},
|
|
23
|
+
"monday": {
|
|
24
|
+
"items": {
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
"type": "array"
|
|
28
|
+
},
|
|
29
|
+
"saturday": {
|
|
30
|
+
"items": {
|
|
31
|
+
"type": "string"
|
|
32
|
+
},
|
|
33
|
+
"type": "array"
|
|
34
|
+
},
|
|
35
|
+
"sunday": {
|
|
36
|
+
"items": {
|
|
37
|
+
"type": "string"
|
|
38
|
+
},
|
|
39
|
+
"type": "array"
|
|
40
|
+
},
|
|
41
|
+
"thursday": {
|
|
42
|
+
"items": {
|
|
43
|
+
"type": "string"
|
|
44
|
+
},
|
|
45
|
+
"type": "array"
|
|
46
|
+
},
|
|
47
|
+
"tuesday": {
|
|
48
|
+
"items": {
|
|
49
|
+
"type": "string"
|
|
50
|
+
},
|
|
51
|
+
"type": "array"
|
|
52
|
+
},
|
|
53
|
+
"wednesday": {
|
|
54
|
+
"items": {
|
|
55
|
+
"type": "string"
|
|
56
|
+
},
|
|
57
|
+
"type": "array"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"type": "object"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"required": [
|
|
64
|
+
"timezone",
|
|
65
|
+
"windows"
|
|
66
|
+
],
|
|
67
|
+
"type": "object"
|
|
68
|
+
},
|
|
5
69
|
"Me3Button": {
|
|
6
70
|
"additionalProperties": false,
|
|
7
71
|
"properties": {
|
|
@@ -65,6 +129,91 @@
|
|
|
65
129
|
],
|
|
66
130
|
"type": "object"
|
|
67
131
|
},
|
|
132
|
+
"Me3IntentBook": {
|
|
133
|
+
"additionalProperties": false,
|
|
134
|
+
"description": "Booking/scheduling intent. Declares that the person accepts meeting bookings.",
|
|
135
|
+
"properties": {
|
|
136
|
+
"availability": {
|
|
137
|
+
"$ref": "#/definitions/Me3BookingAvailability",
|
|
138
|
+
"description": "Availability windows - for native me3 booking"
|
|
139
|
+
},
|
|
140
|
+
"description": {
|
|
141
|
+
"description": "What the meeting is about",
|
|
142
|
+
"type": "string"
|
|
143
|
+
},
|
|
144
|
+
"duration": {
|
|
145
|
+
"description": "Meeting duration in minutes",
|
|
146
|
+
"type": "number"
|
|
147
|
+
},
|
|
148
|
+
"enabled": {
|
|
149
|
+
"description": "Whether booking is enabled",
|
|
150
|
+
"type": "boolean"
|
|
151
|
+
},
|
|
152
|
+
"provider": {
|
|
153
|
+
"description": "Booking provider (e.g., \"cal.com\", \"calendly\") - for external providers",
|
|
154
|
+
"type": "string"
|
|
155
|
+
},
|
|
156
|
+
"title": {
|
|
157
|
+
"description": "Meeting title (e.g., \"30-min Consultation\")",
|
|
158
|
+
"type": "string"
|
|
159
|
+
},
|
|
160
|
+
"url": {
|
|
161
|
+
"description": "Direct booking URL - for external booking systems",
|
|
162
|
+
"type": "string"
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"required": [
|
|
166
|
+
"enabled"
|
|
167
|
+
],
|
|
168
|
+
"type": "object"
|
|
169
|
+
},
|
|
170
|
+
"Me3IntentSubscribe": {
|
|
171
|
+
"additionalProperties": false,
|
|
172
|
+
"description": "Newsletter subscription intent. When enabled, the site accepts email subscriptions via POST /api/subscribe",
|
|
173
|
+
"properties": {
|
|
174
|
+
"description": {
|
|
175
|
+
"description": "What subscribers will receive - for agents to explain the value",
|
|
176
|
+
"type": "string"
|
|
177
|
+
},
|
|
178
|
+
"enabled": {
|
|
179
|
+
"description": "Whether newsletter signups are enabled",
|
|
180
|
+
"type": "boolean"
|
|
181
|
+
},
|
|
182
|
+
"frequency": {
|
|
183
|
+
"description": "How often subscribers will hear from you",
|
|
184
|
+
"enum": [
|
|
185
|
+
"daily",
|
|
186
|
+
"weekly",
|
|
187
|
+
"monthly",
|
|
188
|
+
"irregular"
|
|
189
|
+
],
|
|
190
|
+
"type": "string"
|
|
191
|
+
},
|
|
192
|
+
"title": {
|
|
193
|
+
"description": "Newsletter title (e.g., \"AI Weekly\") - for agents to present context",
|
|
194
|
+
"type": "string"
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"required": [
|
|
198
|
+
"enabled"
|
|
199
|
+
],
|
|
200
|
+
"type": "object"
|
|
201
|
+
},
|
|
202
|
+
"Me3Intents": {
|
|
203
|
+
"additionalProperties": false,
|
|
204
|
+
"description": "Intents object - declares what actions visitors/agents can take. This is the machine-readable API contract for interacting with a person.",
|
|
205
|
+
"properties": {
|
|
206
|
+
"book": {
|
|
207
|
+
"$ref": "#/definitions/Me3IntentBook",
|
|
208
|
+
"description": "Meeting booking"
|
|
209
|
+
},
|
|
210
|
+
"subscribe": {
|
|
211
|
+
"$ref": "#/definitions/Me3IntentSubscribe",
|
|
212
|
+
"description": "Newsletter subscription"
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
"type": "object"
|
|
216
|
+
},
|
|
68
217
|
"Me3Links": {
|
|
69
218
|
"additionalProperties": {
|
|
70
219
|
"anyOf": [
|
|
@@ -171,6 +320,10 @@
|
|
|
171
320
|
"description": "Username/handle",
|
|
172
321
|
"type": "string"
|
|
173
322
|
},
|
|
323
|
+
"intents": {
|
|
324
|
+
"$ref": "#/definitions/Me3Intents",
|
|
325
|
+
"description": "Intents - machine-readable actions that visitors/agents can take. This is the API contract for interacting with the person."
|
|
326
|
+
},
|
|
174
327
|
"links": {
|
|
175
328
|
"$ref": "#/definitions/Me3Links",
|
|
176
329
|
"description": "Social and external links"
|