me3-protocol 2.2.0 → 2.4.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,115 +1,126 @@
1
1
  # me3 Protocol (me.json)
2
2
 
3
- The **me3 Protocol** (`me.json`) is a minimal standard for portable personal websites.
3
+ **The place machines check before acting on a person.**
4
4
 
5
- It treats your online identity as a **"Digital Business Card"**—a single JSON file that makes you discoverable by both humans and AI agents, without locking you into a specific platform.
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
- ## Why it exists (brief)
7
+ ## The Problem
8
8
 
9
- People increasingly ask AI to _find_ a person (to hire, collaborate with, or learn from). Scraping arbitrary personal sites is a failure mode for agents: it’s brittle, slow, and ambiguous. `me.json` is a small, predictable identity endpoint that makes discovery and summarization reliable.
9
+ Machines are already making decisions about people:
10
10
 
11
- ## 1. The Specification
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
- ### File Location & Discovery
15
+ Without an authoritative source, they guess. They scrape. They get it wrong.
14
16
 
15
- To be compliant, your `me.json` file must be hosted at one of the following locations (in order of priority):
17
+ Schema.org describes _pages_. `me.json` declares _people_—their identity, their preferences, and their **intents**.
16
18
 
17
- 1. **Primary**: `https://yourdomain.com/me.json`
18
- 2. **Fallback**: `https://yourdomain.com/.well-known/me`
19
+ ## The Solution: Intents
19
20
 
20
- ### Transport & Security
21
+ The core of `me.json` is the `intents` object—machine-readable declarations of what visitors and agents can do:
21
22
 
22
- - **HTTPS Only**: The file must be served over a secure connection.
23
- - **CORS (Cross-Origin Resource Sharing)**: You **MUST** serve the file with the following header:
24
- ```http
25
- Access-Control-Allow-Origin: *
26
- ```
27
- **Why?** This ensures that AI agents running in browsers (e.g., Chrome extensions, web-based assistants) can read your identity file even if they are running on a different domain. Without this, your digital business card is invisible to the tools that help people find you.
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
- ### Content Type
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 file should be served with `application/json` content type.
48
+ **With `me.json`**: The AI reads `intents.book`, confirms it's enabled, and knows exactly where to send the user.
32
49
 
33
- ## 2. The Schema
50
+ That's the protocol's value: **authority before action**.
34
51
 
35
- Your `me.json` defines who you are and where to find you. It is strictly typed to ensure compatibility across all readers.
52
+ ## Supported Intents
36
53
 
37
- ### Core Structure (`Me3Profile`)
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
- | Field | Type | Required | Description |
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
- ### Examples
61
+ ---
54
62
 
55
- Minimal:
63
+ ## Full Schema
56
64
 
57
- ```json
58
- {
59
- "version": "0.1",
60
- "name": "Jane Doe",
61
- "handle": "janedoe",
62
- "bio": "Building the open web. Creative Director at Studio X.",
63
- "location": "Berlin, Germany",
64
- "avatar": "https://example.com/jane.jpg",
65
- "links": {
66
- "website": "https://janedoe.com",
67
- "twitter": "janedoe",
68
- "github": "janedoe"
69
- },
70
- "buttons": [
71
- {
72
- "text": "Hire Me",
73
- "url": "https://cal.com/janedoe",
74
- "style": "primary"
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
- More complete examples live in [`examples/`](./examples/), including [`examples/simple.json`](./examples/simple.json) and [`examples/full.json`](./examples/full.json).
90
+ 1. **Primary**: `https://yourdomain.com/me.json`
91
+ 2. **Fallback**: `https://yourdomain.com/.well-known/me`
81
92
 
82
- ## 3. What it is NOT
93
+ ### Requirements
83
94
 
84
- - **NOT Authentication**: `me.json` is public data. It does not handle logins, passwords, or private keys.
85
- - **NOT a Social Network**: There is no "feed", no "likes", and no central server. You own your data.
86
- - **NOT a Platform**: You can host this file on GitHub Pages, Vercel, WordPress, or your own server.
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
- ## 4. Guardrails & versioning
99
+ ---
90
100
 
91
- - **Current version**: `0.1` (see `version` field). Validators in this repo currently require `version === "0.1"`.
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
- ## 5. Usage
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
- ### For Developers
108
+ ---
98
109
 
99
- You can use this package to validate `me.json` files in your applications.
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
- // Validate an object
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 in [schema.json](./schema.json) for non-TypeScript implementations.
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
@@ -14,6 +14,18 @@ export interface Me3Page {
14
14
  /** Whether to show in navigation */
15
15
  visible: boolean;
16
16
  }
17
+ export interface Me3Post {
18
+ /** URL-friendly identifier */
19
+ slug: string;
20
+ /** Display name for listing */
21
+ title: string;
22
+ /** Path to markdown file (relative to me.json) */
23
+ file: string;
24
+ /** ISO publish date (optional) */
25
+ publishedAt?: string;
26
+ /** Short excerpt for archive/listing (optional) */
27
+ excerpt?: string;
28
+ }
17
29
  export interface Me3Links {
18
30
  website?: string;
19
31
  github?: string;
@@ -61,6 +73,24 @@ export interface Me3IntentSubscribe {
61
73
  /** How often subscribers will hear from you */
62
74
  frequency?: "daily" | "weekly" | "monthly" | "irregular";
63
75
  }
76
+ /**
77
+ * Availability windows for booking.
78
+ * Defines when the person is available for meetings.
79
+ */
80
+ export interface Me3BookingAvailability {
81
+ /** Timezone for the availability windows (e.g., "America/New_York") */
82
+ timezone: string;
83
+ /** Weekly availability windows by day */
84
+ windows: {
85
+ monday?: string[];
86
+ tuesday?: string[];
87
+ wednesday?: string[];
88
+ thursday?: string[];
89
+ friday?: string[];
90
+ saturday?: string[];
91
+ sunday?: string[];
92
+ };
93
+ }
64
94
  /**
65
95
  * Booking/scheduling intent.
66
96
  * Declares that the person accepts meeting bookings.
@@ -74,10 +104,12 @@ export interface Me3IntentBook {
74
104
  description?: string;
75
105
  /** Meeting duration in minutes */
76
106
  duration?: number;
77
- /** Booking provider (e.g., "cal.com", "calendly") */
107
+ /** Booking provider (e.g., "cal.com", "calendly") - for external providers */
78
108
  provider?: string;
79
- /** Direct booking URL */
80
- url: string;
109
+ /** Direct booking URL - for external booking systems */
110
+ url?: string;
111
+ /** Availability windows - for native me3 booking */
112
+ availability?: Me3BookingAvailability;
81
113
  }
82
114
  /**
83
115
  * Intents object - declares what actions visitors/agents can take.
@@ -110,6 +142,8 @@ export interface Me3Profile {
110
142
  buttons?: Me3Button[];
111
143
  /** Custom pages (markdown) */
112
144
  pages?: Me3Page[];
145
+ /** Blog posts (markdown) */
146
+ posts?: Me3Post[];
113
147
  /**
114
148
  * Custom footer configuration.
115
149
  * - `undefined`: default footer behavior (renderer-defined)
package/dist/index.js CHANGED
@@ -277,6 +277,55 @@ function validateProfile(data) {
277
277
  });
278
278
  }
279
279
  }
280
+ // Posts (optional)
281
+ if (profile.posts !== undefined) {
282
+ const posts = profile.posts;
283
+ if (!Array.isArray(posts)) {
284
+ errors.push({ field: "posts", message: "Posts must be an array" });
285
+ }
286
+ else {
287
+ posts.forEach((post, index) => {
288
+ if (!post || typeof post !== "object") {
289
+ errors.push({
290
+ field: `posts[${index}]`,
291
+ message: "Post must be an object",
292
+ });
293
+ return;
294
+ }
295
+ if (!post.slug || typeof post.slug !== "string") {
296
+ errors.push({
297
+ field: `posts[${index}].slug`,
298
+ message: "Post slug is required",
299
+ });
300
+ }
301
+ if (!post.title || typeof post.title !== "string") {
302
+ errors.push({
303
+ field: `posts[${index}].title`,
304
+ message: "Post title is required",
305
+ });
306
+ }
307
+ if (!post.file || typeof post.file !== "string") {
308
+ errors.push({
309
+ field: `posts[${index}].file`,
310
+ message: "Post file is required",
311
+ });
312
+ }
313
+ if (post.publishedAt !== undefined &&
314
+ typeof post.publishedAt !== "string") {
315
+ errors.push({
316
+ field: `posts[${index}].publishedAt`,
317
+ message: "Post publishedAt must be a string",
318
+ });
319
+ }
320
+ if (post.excerpt !== undefined && typeof post.excerpt !== "string") {
321
+ errors.push({
322
+ field: `posts[${index}].excerpt`,
323
+ message: "Post excerpt must be a string",
324
+ });
325
+ }
326
+ });
327
+ }
328
+ }
280
329
  // Intents (optional)
281
330
  if (profile.intents !== undefined) {
282
331
  if (typeof profile.intents !== "object" || profile.intents === null) {
@@ -396,16 +445,93 @@ function validateProfile(data) {
396
445
  message: "Book provider must be a string",
397
446
  });
398
447
  }
399
- if (!book.url || typeof book.url !== "string") {
400
- errors.push({
401
- field: "intents.book.url",
402
- message: "Book URL is required",
403
- });
448
+ // URL is optional if availability is set (native me3 booking)
449
+ if (book.url !== undefined) {
450
+ if (typeof book.url !== "string") {
451
+ errors.push({
452
+ field: "intents.book.url",
453
+ message: "Book URL must be a string",
454
+ });
455
+ }
456
+ else if (!URL_REGEX.test(book.url)) {
457
+ errors.push({
458
+ field: "intents.book.url",
459
+ message: "Book URL must be a valid URL starting with http:// or https://",
460
+ });
461
+ }
462
+ }
463
+ // Validate availability if present
464
+ if (book.availability !== undefined) {
465
+ if (typeof book.availability !== "object" ||
466
+ book.availability === null) {
467
+ errors.push({
468
+ field: "intents.book.availability",
469
+ message: "Book availability must be an object",
470
+ });
471
+ }
472
+ else {
473
+ const availability = book.availability;
474
+ if (!availability.timezone ||
475
+ typeof availability.timezone !== "string") {
476
+ errors.push({
477
+ field: "intents.book.availability.timezone",
478
+ message: "Availability timezone is required",
479
+ });
480
+ }
481
+ if (availability.windows !== undefined) {
482
+ if (typeof availability.windows !== "object" ||
483
+ availability.windows === null) {
484
+ errors.push({
485
+ field: "intents.book.availability.windows",
486
+ message: "Availability windows must be an object",
487
+ });
488
+ }
489
+ else {
490
+ const windows = availability.windows;
491
+ const validDays = [
492
+ "monday",
493
+ "tuesday",
494
+ "wednesday",
495
+ "thursday",
496
+ "friday",
497
+ "saturday",
498
+ "sunday",
499
+ ];
500
+ const timeWindowRegex = /^\d{2}:\d{2}-\d{2}:\d{2}$/;
501
+ for (const [day, value] of Object.entries(windows)) {
502
+ if (!validDays.includes(day)) {
503
+ errors.push({
504
+ field: `intents.book.availability.windows.${day}`,
505
+ message: `Invalid day: ${day}`,
506
+ });
507
+ continue;
508
+ }
509
+ if (!Array.isArray(value)) {
510
+ errors.push({
511
+ field: `intents.book.availability.windows.${day}`,
512
+ message: `Windows for ${day} must be an array`,
513
+ });
514
+ continue;
515
+ }
516
+ for (const window of value) {
517
+ if (typeof window !== "string" ||
518
+ !timeWindowRegex.test(window)) {
519
+ errors.push({
520
+ field: `intents.book.availability.windows.${day}`,
521
+ message: `Invalid time window format. Use HH:MM-HH:MM`,
522
+ });
523
+ }
524
+ }
525
+ }
526
+ }
527
+ }
528
+ }
404
529
  }
405
- else if (!URL_REGEX.test(book.url)) {
530
+ // Either URL or availability must be set for booking to work
531
+ if (!book.url && !book.availability) {
406
532
  errors.push({
407
- field: "intents.book.url",
408
- message: "Book URL must be a valid URL starting with http:// or https://",
533
+ field: "intents.book",
534
+ message: "Book intent requires either a URL (for external booking) or availability (for native booking)",
409
535
  });
410
536
  }
411
537
  }
@@ -28,6 +28,15 @@
28
28
  "pages": [
29
29
  { "slug": "about", "title": "About", "file": "about.md", "visible": true }
30
30
  ],
31
+ "posts": [
32
+ {
33
+ "slug": "hello-world",
34
+ "title": "Hello World",
35
+ "file": "blog/hello-world.md",
36
+ "publishedAt": "2026-01-29T12:00:00.000Z",
37
+ "excerpt": "A short welcome post for the blog."
38
+ }
39
+ ],
31
40
  "intents": {
32
41
  "subscribe": {
33
42
  "enabled": true,
@@ -40,8 +49,16 @@
40
49
  "title": "30-min Consultation",
41
50
  "description": "Let's discuss your project",
42
51
  "duration": 30,
43
- "provider": "cal.com",
44
- "url": "https://cal.com/example"
52
+ "availability": {
53
+ "timezone": "America/New_York",
54
+ "windows": {
55
+ "monday": ["09:00-12:00", "14:00-17:00"],
56
+ "tuesday": ["09:00-17:00"],
57
+ "wednesday": ["09:00-17:00"],
58
+ "thursday": ["09:00-17:00"],
59
+ "friday": ["09:00-12:00"]
60
+ }
61
+ }
45
62
  }
46
63
  }
47
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "me3-protocol",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
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": {
@@ -69,6 +133,10 @@
69
133
  "additionalProperties": false,
70
134
  "description": "Booking/scheduling intent. Declares that the person accepts meeting bookings.",
71
135
  "properties": {
136
+ "availability": {
137
+ "$ref": "#/definitions/Me3BookingAvailability",
138
+ "description": "Availability windows - for native me3 booking"
139
+ },
72
140
  "description": {
73
141
  "description": "What the meeting is about",
74
142
  "type": "string"
@@ -82,7 +150,7 @@
82
150
  "type": "boolean"
83
151
  },
84
152
  "provider": {
85
- "description": "Booking provider (e.g., \"cal.com\", \"calendly\")",
153
+ "description": "Booking provider (e.g., \"cal.com\", \"calendly\") - for external providers",
86
154
  "type": "string"
87
155
  },
88
156
  "title": {
@@ -90,13 +158,12 @@
90
158
  "type": "string"
91
159
  },
92
160
  "url": {
93
- "description": "Direct booking URL",
161
+ "description": "Direct booking URL - for external booking systems",
94
162
  "type": "string"
95
163
  }
96
164
  },
97
165
  "required": [
98
- "enabled",
99
- "url"
166
+ "enabled"
100
167
  ],
101
168
  "type": "object"
102
169
  },
@@ -215,6 +282,37 @@
215
282
  ],
216
283
  "type": "object"
217
284
  },
285
+ "Me3Post": {
286
+ "additionalProperties": false,
287
+ "properties": {
288
+ "excerpt": {
289
+ "description": "Short excerpt for archive/listing (optional)",
290
+ "type": "string"
291
+ },
292
+ "file": {
293
+ "description": "Path to markdown file (relative to me.json)",
294
+ "type": "string"
295
+ },
296
+ "publishedAt": {
297
+ "description": "ISO publish date (optional)",
298
+ "type": "string"
299
+ },
300
+ "slug": {
301
+ "description": "URL-friendly identifier",
302
+ "type": "string"
303
+ },
304
+ "title": {
305
+ "description": "Display name for listing",
306
+ "type": "string"
307
+ }
308
+ },
309
+ "required": [
310
+ "slug",
311
+ "title",
312
+ "file"
313
+ ],
314
+ "type": "object"
315
+ },
218
316
  "Me3Profile": {
219
317
  "additionalProperties": false,
220
318
  "properties": {
@@ -276,6 +374,13 @@
276
374
  },
277
375
  "type": "array"
278
376
  },
377
+ "posts": {
378
+ "description": "Blog posts (markdown)",
379
+ "items": {
380
+ "$ref": "#/definitions/Me3Post"
381
+ },
382
+ "type": "array"
383
+ },
279
384
  "version": {
280
385
  "description": "Protocol version",
281
386
  "type": "string"