@amtp/protocol 1.0.1
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/LICENSE +21 -0
- package/README.md +386 -0
- package/USAGE_GUIDE.md +722 -0
- package/bin/amtp.ts +387 -0
- package/dist/client/amtp-client.d.ts +164 -0
- package/dist/client/amtp-client.js +460 -0
- package/dist/client/amtp-client.js.map +1 -0
- package/dist/client/examples/basic-client.d.ts +6 -0
- package/dist/client/examples/basic-client.js +35 -0
- package/dist/client/examples/basic-client.js.map +1 -0
- package/dist/crawler/amtp-crawler.d.ts +125 -0
- package/dist/crawler/amtp-crawler.js +359 -0
- package/dist/crawler/amtp-crawler.js.map +1 -0
- package/dist/crawler/examples/basic-crawler.d.ts +6 -0
- package/dist/crawler/examples/basic-crawler.js +28 -0
- package/dist/crawler/examples/basic-crawler.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +70 -0
- package/dist/index.js.map +1 -0
- package/dist/server/adapters/fastify-adapter.d.ts +86 -0
- package/dist/server/adapters/fastify-adapter.js +169 -0
- package/dist/server/adapters/fastify-adapter.js.map +1 -0
- package/dist/server/amtp-ql-executor.d.ts +24 -0
- package/dist/server/amtp-ql-executor.js +198 -0
- package/dist/server/amtp-ql-executor.js.map +1 -0
- package/dist/server/amtp-ql-parser.d.ts +30 -0
- package/dist/server/amtp-ql-parser.js +212 -0
- package/dist/server/amtp-ql-parser.js.map +1 -0
- package/dist/server/amtp-server.d.ts +183 -0
- package/dist/server/amtp-server.js +650 -0
- package/dist/server/amtp-server.js.map +1 -0
- package/dist/server/examples/basic-server.d.ts +6 -0
- package/dist/server/examples/basic-server.js +215 -0
- package/dist/server/examples/basic-server.js.map +1 -0
- package/dist/server/examples/saas-dashboard-server.d.ts +44 -0
- package/dist/server/examples/saas-dashboard-server.js +387 -0
- package/dist/server/examples/saas-dashboard-server.js.map +1 -0
- package/dist/server/markdown-parser.d.ts +31 -0
- package/dist/server/markdown-parser.js +463 -0
- package/dist/server/markdown-parser.js.map +1 -0
- package/dist/server/notifications.d.ts +40 -0
- package/dist/server/notifications.js +134 -0
- package/dist/server/notifications.js.map +1 -0
- package/dist/server/permissions.d.ts +40 -0
- package/dist/server/permissions.js +156 -0
- package/dist/server/permissions.js.map +1 -0
- package/dist/server/security.d.ts +127 -0
- package/dist/server/security.js +368 -0
- package/dist/server/security.js.map +1 -0
- package/dist/types/amtp.types.d.ts +720 -0
- package/dist/types/amtp.types.js +224 -0
- package/dist/types/amtp.types.js.map +1 -0
- package/package.json +89 -0
package/USAGE_GUIDE.md
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
# AMTP Usage Guide
|
|
2
|
+
|
|
3
|
+
Practical walkthrough for running, extending, and integrating with the AMTP protocol toolkit.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- **Node.js** >= 18.0.0
|
|
10
|
+
- **npm** >= 9.0.0
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
git clone <repo-url> && cd AMTP
|
|
14
|
+
npm install
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 1. CLI Reference (`amtp`)
|
|
20
|
+
|
|
21
|
+
The CLI is run via `ts-node`:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm run amtp -- <command> [options]
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### `amtp init <dir>`
|
|
28
|
+
Scaffold a new AMTP project with starter files.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm run amtp -- init my-amtp-app
|
|
32
|
+
cd my-amtp-app && npm install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### `amtp serve`
|
|
36
|
+
Start a local AMTP development server.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm run amtp -- serve -p 3000 -h 0.0.0.0
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### `amtp crawl <url>`
|
|
43
|
+
Crawl an AMTP-enabled site and dump the index as JSON.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm run amtp -- crawl https://example.com --max-pages 20 --max-depth 3
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `amtp validate <files...>`
|
|
50
|
+
Check markdown files for AMTP structural validity.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run amtp -- validate spec/examples/*.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `amtp doctor`
|
|
57
|
+
Run all health checks — type-check, build, lint, tests, audit, Node version, `.gitignore`, env safety.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm run amtp -- doctor
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Built-in shorthand scripts
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm run server # Start basic-server on :3000
|
|
67
|
+
npm run server:saas # Start SaaS dashboard on :3000
|
|
68
|
+
npm run client # Run basic-client example
|
|
69
|
+
npm run crawler # Run basic-crawler example
|
|
70
|
+
npm run bench # Run performance benchmarks
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 2. Building an AMTP Server
|
|
76
|
+
|
|
77
|
+
### Minimal Server
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// server.ts
|
|
81
|
+
import { AMTPServer } from "./src/server/amtp-server";
|
|
82
|
+
import { AMTPResponseBuilder } from "./src/server/amtp-server";
|
|
83
|
+
|
|
84
|
+
const server = new AMTPServer({ port: 3000, enableCORS: true });
|
|
85
|
+
|
|
86
|
+
server.register("GET", "/", (_req, res) => {
|
|
87
|
+
const doc = new AMTPResponseBuilder().build("# Hello\n\nAMTP world.\n\n## Actions\n\n[GREET] — Say hello");
|
|
88
|
+
const { headers, body } = doc;
|
|
89
|
+
for (const [k, v] of Object.entries(headers)) if (v) res.setHeader(k, v);
|
|
90
|
+
res.send(body);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
server.start();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Server with Product Page, Actions, Batch & AMTP-QL
|
|
97
|
+
|
|
98
|
+
A complete example at `src/server/examples/basic-server.ts` includes:
|
|
99
|
+
|
|
100
|
+
| Route | Purpose |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `GET /products/:id` | Product page as AMTP document |
|
|
103
|
+
| `POST /products/:id/buy` | Action handler with notification emission |
|
|
104
|
+
| `POST /api/amtp/batch` | Batch action execution (v1.1) |
|
|
105
|
+
| `POST /api/amtp/query` | AMTP-QL query endpoint (v2.0) |
|
|
106
|
+
| `POST /api/webhooks` | Register a push webhook |
|
|
107
|
+
| `GET /api/amtp/stream` | SSE streaming endpoint |
|
|
108
|
+
| `GET /health` | Health check |
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm run server
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### SaaS Multi-Tenant Dashboard Server
|
|
115
|
+
|
|
116
|
+
Located at `src/server/examples/saas-dashboard-server.ts`. Uses SQLite + JWT auth.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm run server:saas
|
|
120
|
+
|
|
121
|
+
# Demo credentials:
|
|
122
|
+
# email: demo@example.com
|
|
123
|
+
# password: demo-password
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Endpoints:
|
|
127
|
+
|
|
128
|
+
| Route | Auth | Purpose |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `POST /api/v1/auth/register` | No | Register tenant + user |
|
|
131
|
+
| `POST /api/v1/auth/login` | No | Login, receive JWT |
|
|
132
|
+
| `GET /api/v1/me` | JWT | Current user info |
|
|
133
|
+
| `GET /api/v1/workspaces` | JWT | List workspaces |
|
|
134
|
+
| `POST /api/v1/workspaces` | JWT | Create workspace |
|
|
135
|
+
| `GET /amtp/*` | JWT | AMTP content-negotiated workspace list |
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# Get an AMTP document
|
|
139
|
+
curl -H "Accept: text/amtp+markdown" \
|
|
140
|
+
-H "Authorization: Bearer <token>" \
|
|
141
|
+
http://localhost:3000/
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Using the Middleware Stack Directly
|
|
145
|
+
|
|
146
|
+
For custom Express apps, compose individual middleware:
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import express from "express";
|
|
150
|
+
import { AMTPMiddlewareFactory } from "./src/server/amtp-server";
|
|
151
|
+
|
|
152
|
+
const app = express();
|
|
153
|
+
const amtp = new AMTPMiddlewareFactory();
|
|
154
|
+
|
|
155
|
+
app.use(amtp.securityHeaders());
|
|
156
|
+
app.use(amtp.cors(["https://myapp.com"]));
|
|
157
|
+
app.use(amtp.contentNegotiation());
|
|
158
|
+
app.use(amtp.rateLimit(60_000, 100));
|
|
159
|
+
app.use(amtp.csrfGuard());
|
|
160
|
+
app.use(amtp.middleware()); // parses AMTP headers, resolves session
|
|
161
|
+
app.use(amtp.responseSender()); // sends context.response.body
|
|
162
|
+
app.use(amtp.errorHandler()); // catch-all error handler
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Fastify Adapter
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import fastify from "fastify";
|
|
169
|
+
import { fastifyAMTP, amtpReply } from "./src/server/adapters/fastify-adapter";
|
|
170
|
+
import { AMTPResponseBuilder } from "./src/server/amtp-server";
|
|
171
|
+
|
|
172
|
+
const app = fastify({ logger: true });
|
|
173
|
+
amtpReply(app);
|
|
174
|
+
|
|
175
|
+
await app.register(fastifyAMTP.plugin, {
|
|
176
|
+
trustedOrigins: ["https://example.com"],
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
app.get("/hello", async (_req, reply) => {
|
|
180
|
+
const doc = new AMTPResponseBuilder().build("# Hello\n\nFastify + AMTP");
|
|
181
|
+
return reply.amtp(doc);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
await app.listen({ port: 3000 });
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 3. Agent Client SDK
|
|
190
|
+
|
|
191
|
+
### Basic Usage
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { AMTPClient } from "./src/client/amtp-client";
|
|
195
|
+
|
|
196
|
+
const client = new AMTPClient({
|
|
197
|
+
baseUrl: "http://localhost:3000",
|
|
198
|
+
capabilities: ["actions", "forms", "streaming"],
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Fetch a page
|
|
202
|
+
const page = await client.getPage("/products/demo");
|
|
203
|
+
console.log(page.title); // "Product demo"
|
|
204
|
+
console.log(page.actions); // [{ id: "buy", ... }, { id: "add_to_cart", ... }]
|
|
205
|
+
console.log(page.forms); // any forms on the page
|
|
206
|
+
console.log(page.links); // navigation links
|
|
207
|
+
|
|
208
|
+
// Execute an action
|
|
209
|
+
const result = await client.executeAction("buy", {
|
|
210
|
+
productId: "demo",
|
|
211
|
+
quantity: 2,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Submit a form (e.g. checkout)
|
|
215
|
+
const form = page.forms[0];
|
|
216
|
+
await client.submitForm(form, {
|
|
217
|
+
email: "agent@example.com",
|
|
218
|
+
address: "123 AI Street",
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Stream real-time updates
|
|
222
|
+
const es = client.streamUpdates("/api/amtp/stream", (update) => {
|
|
223
|
+
console.log("🔔", update.type, update.data);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Login / Logout
|
|
227
|
+
const { sessionId } = await client.login("user", "pass");
|
|
228
|
+
await client.logout();
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Autonomous Agent Example
|
|
232
|
+
|
|
233
|
+
The `AutonomousAgent` class demonstrates an agent that autonomously shops:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { AutonomousAgent } from "./src/client/amtp-client";
|
|
237
|
+
|
|
238
|
+
const agent = new AutonomousAgent({
|
|
239
|
+
baseUrl: "http://localhost:3000",
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const result = await agent.autonomousShop("laptop", 2500);
|
|
243
|
+
console.log(result.success ? "✅ Purchase completed" : "❌ Failed");
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The workflow:
|
|
247
|
+
1. Search products by keyword
|
|
248
|
+
2. Find a product within budget
|
|
249
|
+
3. View the product page
|
|
250
|
+
4. Execute `add_to_cart` action
|
|
251
|
+
5. Navigate to cart
|
|
252
|
+
6. Get checkout forms
|
|
253
|
+
|
|
254
|
+
### AMTP-QL Queries (v2.0)
|
|
255
|
+
|
|
256
|
+
Project only the fields you need:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const result = await client.query(`
|
|
260
|
+
query {
|
|
261
|
+
document {
|
|
262
|
+
title
|
|
263
|
+
actions { id description }
|
|
264
|
+
nodes(type: ["HEADING"], limit: 5) { type content }
|
|
265
|
+
pagination { pageInfo { hasNextPage endCursor } }
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
`);
|
|
269
|
+
console.log(result.title);
|
|
270
|
+
console.log(result.actions);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 4. Crawler & Search Indexer
|
|
276
|
+
|
|
277
|
+
### Crawl a Site
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
import { AMTPCrawler } from "./src/crawler/amtp-crawler";
|
|
281
|
+
|
|
282
|
+
const crawler = new AMTPCrawler({
|
|
283
|
+
baseUrl: "http://localhost:3000",
|
|
284
|
+
maxPages: 100,
|
|
285
|
+
maxDepth: 3,
|
|
286
|
+
respectRobotsTxt: true,
|
|
287
|
+
delays: { betweenRequests: 200, betweenDomains: 1000 },
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const pages = await crawler.crawl();
|
|
291
|
+
console.log(`Crawled ${pages.length} pages`);
|
|
292
|
+
|
|
293
|
+
// Search the in-memory index
|
|
294
|
+
const results = crawler.search("macbook");
|
|
295
|
+
console.log(results);
|
|
296
|
+
|
|
297
|
+
// Export index as JSON
|
|
298
|
+
fs.writeFileSync("crawl-index.json", crawler.exportIndex());
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Search Indexer
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { SearchIndexer } from "./src/crawler/amtp-crawler";
|
|
305
|
+
|
|
306
|
+
const indexer = new SearchIndexer();
|
|
307
|
+
indexer.addPages(pages);
|
|
308
|
+
|
|
309
|
+
const results = indexer.search("laptop", 10); // top 10 by relevance
|
|
310
|
+
console.log(results.map(r => r.title));
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## 5. AMTP Markdown Format
|
|
316
|
+
|
|
317
|
+
AMTP documents extend standard markdown with structured blocks:
|
|
318
|
+
|
|
319
|
+
### Metadata Block
|
|
320
|
+
```markdown
|
|
321
|
+
```amtp-meta
|
|
322
|
+
{
|
|
323
|
+
"pageId": "prod-123",
|
|
324
|
+
"pageType": "product",
|
|
325
|
+
"version": "1.0",
|
|
326
|
+
"sessionRequired": false
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Actions Section
|
|
332
|
+
```markdown
|
|
333
|
+
## Actions
|
|
334
|
+
|
|
335
|
+
[BUY] — Purchase immediately
|
|
336
|
+
[ADD_TO_CART] — Add to shopping cart
|
|
337
|
+
[REVIEW] — Leave a review
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Form Definition
|
|
341
|
+
```markdown
|
|
342
|
+
## Login Form
|
|
343
|
+
ACTION: login
|
|
344
|
+
METHOD: POST
|
|
345
|
+
ENDPOINT: /api/login
|
|
346
|
+
FIELD: email
|
|
347
|
+
TYPE: email
|
|
348
|
+
REQUIRED: true
|
|
349
|
+
LABEL: Email Address
|
|
350
|
+
FIELD: password
|
|
351
|
+
TYPE: password
|
|
352
|
+
REQUIRED: true
|
|
353
|
+
LABEL: Password
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Structured Data
|
|
357
|
+
```markdown
|
|
358
|
+
```amtp-data
|
|
359
|
+
{
|
|
360
|
+
"@type": "Product",
|
|
361
|
+
"name": "MacBook Pro 14",
|
|
362
|
+
"price": 1999,
|
|
363
|
+
"currency": "USD",
|
|
364
|
+
"inStock": true
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Pagination Block
|
|
370
|
+
```markdown
|
|
371
|
+
```amtp-pagination
|
|
372
|
+
{
|
|
373
|
+
"pageInfo": { "hasNextPage": true, "endCursor": "c2" },
|
|
374
|
+
"nextCursor": "c2",
|
|
375
|
+
"totalItems": 42
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Structured Action Definition (v2.0)
|
|
381
|
+
Replaces or supplements simple `[ACTION]` tokens with full JSON action metadata including typed parameters and permission requirements:
|
|
382
|
+
|
|
383
|
+
```markdown
|
|
384
|
+
```amtp-action
|
|
385
|
+
[
|
|
386
|
+
{
|
|
387
|
+
"id": "delete_doc",
|
|
388
|
+
"label": "Delete Document",
|
|
389
|
+
"method": "POST",
|
|
390
|
+
"endpoint": "/api/docs/delete",
|
|
391
|
+
"permissions": ["doc:delete"],
|
|
392
|
+
"parameters": [
|
|
393
|
+
{ "name": "docId", "type": "string", "required": true }
|
|
394
|
+
]
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
```
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Permissions Block (v2.0)
|
|
401
|
+
Declares permissions that control access to actions on resources:
|
|
402
|
+
|
|
403
|
+
```markdown
|
|
404
|
+
```amtp-permissions
|
|
405
|
+
[
|
|
406
|
+
{
|
|
407
|
+
"id": "doc:read",
|
|
408
|
+
"name": "Read Documents",
|
|
409
|
+
"resource": "doc:*",
|
|
410
|
+
"actions": ["view_doc", "search_docs"]
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
"id": "doc:delete",
|
|
414
|
+
"name": "Delete Documents",
|
|
415
|
+
"resource": "doc:*",
|
|
416
|
+
"actions": ["delete_doc"]
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
```
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Policy Block (v2.0)
|
|
423
|
+
Binds permissions to roles and optional conditions. Policies are evaluated at action-execution time:
|
|
424
|
+
|
|
425
|
+
```markdown
|
|
426
|
+
```amtp-policy
|
|
427
|
+
[
|
|
428
|
+
{
|
|
429
|
+
"id": "admin-policy",
|
|
430
|
+
"name": "Admin Access",
|
|
431
|
+
"permissions": ["doc:read", "doc:write", "doc:delete"],
|
|
432
|
+
"roles": ["admin"],
|
|
433
|
+
"priority": 100
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"id": "viewer-policy",
|
|
437
|
+
"name": "Viewer Access",
|
|
438
|
+
"permissions": ["doc:read"],
|
|
439
|
+
"roles": ["viewer"]
|
|
440
|
+
}
|
|
441
|
+
]
|
|
442
|
+
```
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Supported condition operators: `eq`, `neq`, `in`, `gt`, `lt`, `contains`, `exists`.
|
|
446
|
+
|
|
447
|
+
### Skill Block (v2.0)
|
|
448
|
+
Bundles actions, permissions, and dependencies into a named capability group:
|
|
449
|
+
|
|
450
|
+
```markdown
|
|
451
|
+
```amtp-skill
|
|
452
|
+
[
|
|
453
|
+
{
|
|
454
|
+
"id": "content-manager",
|
|
455
|
+
"name": "Content Management",
|
|
456
|
+
"actions": ["view_doc", "edit_doc", "delete_doc"],
|
|
457
|
+
"permissions": ["doc:read", "doc:write", "doc:delete"],
|
|
458
|
+
"requires": ["login"]
|
|
459
|
+
}
|
|
460
|
+
]
|
|
461
|
+
```
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## 6. Permission Guard — Policy-Based Access Control
|
|
467
|
+
|
|
468
|
+
The `PermissionGuard` is a server-side middleware that enforces the permissions and policies declared in AMTP documents at action-execution time.
|
|
469
|
+
|
|
470
|
+
### How It Works
|
|
471
|
+
|
|
472
|
+
1. A document declares `permissions` and `policies` as first-class blocks
|
|
473
|
+
2. A session holds a flat list of granted permission IDs (e.g. `["doc:read", "doc:delete"]`)
|
|
474
|
+
3. The session also has a `role` in its metadata (e.g. `"admin"`, `"viewer"`)
|
|
475
|
+
4. When an action is requested, `PermissionGuard.check()` finds matching policies by role, evaluates conditions, and verifies the session has all required permissions
|
|
476
|
+
|
|
477
|
+
### Usage
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
import { PermissionGuard } from "./src/server/permissions";
|
|
481
|
+
import { AMTPError } from "./src/types/amtp.types";
|
|
482
|
+
|
|
483
|
+
const guard = new PermissionGuard();
|
|
484
|
+
|
|
485
|
+
// Inside an action handler:
|
|
486
|
+
app.post("/api/amtp/action", (req, res) => {
|
|
487
|
+
const action = findAction(req.body.action); // look up the action definition
|
|
488
|
+
const doc = getCurrentDocument(); // the AMTP document context
|
|
489
|
+
const session = req.session; // authenticated session with permissions
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
guard.assert(action, doc, session); // throws AMTPError if denied
|
|
493
|
+
executeAction(action, req.body.parameters);
|
|
494
|
+
res.json({ status: "success" });
|
|
495
|
+
} catch (err) {
|
|
496
|
+
if (err instanceof AMTPError) {
|
|
497
|
+
res.status(err.statusCode).json({
|
|
498
|
+
error: { code: err.code, message: err.message }
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Configuration
|
|
506
|
+
|
|
507
|
+
```typescript
|
|
508
|
+
const guard = new PermissionGuard({
|
|
509
|
+
denyByDefault: true, // deny actions that have no matching policy (default: false)
|
|
510
|
+
deniedMessage: "Insufficient permissions", // custom error message
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
// Non-throwing check:
|
|
514
|
+
const result = guard.check(action, doc, session);
|
|
515
|
+
if (result.allowed) {
|
|
516
|
+
console.log(`Matched policy: ${result.matchedPolicy}`);
|
|
517
|
+
} else {
|
|
518
|
+
console.log(`Denied: ${result.reason}`);
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Full Example: Document Management API
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
import { PermissionGuard } from "./src/server/permissions";
|
|
526
|
+
import { AMTPServer, AMTPResponseBuilder } from "./src/server/amtp-server";
|
|
527
|
+
import type { AMTPDocument, Session } from "./src/types/amtp.types";
|
|
528
|
+
|
|
529
|
+
const guard = new PermissionGuard();
|
|
530
|
+
const server = new AMTPServer({ port: 3000 });
|
|
531
|
+
|
|
532
|
+
const securedDoc: AMTPDocument = {
|
|
533
|
+
type: "document",
|
|
534
|
+
version: "2.0",
|
|
535
|
+
title: "Document Manager",
|
|
536
|
+
path: "/docs",
|
|
537
|
+
nodes: [],
|
|
538
|
+
actions: [
|
|
539
|
+
{ id: "delete_doc", label: "Delete", method: "POST" as any, endpoint: "/api/docs/delete", permissions: ["doc:delete"] },
|
|
540
|
+
{ id: "view_doc", label: "View", method: "GET" as any, endpoint: "/api/docs/view", permissions: ["doc:read"] },
|
|
541
|
+
],
|
|
542
|
+
forms: [],
|
|
543
|
+
links: [],
|
|
544
|
+
metadata: {},
|
|
545
|
+
permissions: [
|
|
546
|
+
{ id: "doc:read", name: "Read Documents", resource: "doc:*", actions: ["view_doc"] },
|
|
547
|
+
{ id: "doc:delete", name: "Delete Documents", resource: "doc:*", actions: ["delete_doc"] },
|
|
548
|
+
],
|
|
549
|
+
policies: [
|
|
550
|
+
{ id: "admin-policy", name: "Admin", permissions: ["doc:read", "doc:delete"], roles: ["admin"] },
|
|
551
|
+
{ id: "viewer-policy", name: "Viewer", permissions: ["doc:read"], roles: ["viewer"] },
|
|
552
|
+
],
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
server.register("POST", "/api/docs/delete", (req: any, res: any) => {
|
|
556
|
+
const session: Session = req.amtpContext?.session;
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
guard.assert(securedDoc.actions[0], securedDoc, session);
|
|
560
|
+
res.json({ status: "success", deleted: true });
|
|
561
|
+
} catch (err: any) {
|
|
562
|
+
res.status(err.statusCode || 403).json({ error: err.message });
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
server.start();
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## 7. Use Cases
|
|
572
|
+
|
|
573
|
+
### Use Case 1: E-Commerce Shopping Agent
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
// 1. Server exposes product catalog as AMTP documents
|
|
577
|
+
// GET /products/mbp-14 → Markdown with price, specs, actions
|
|
578
|
+
// GET /cart → Current cart state
|
|
579
|
+
// POST /api/amtp/action → Execute checkout
|
|
580
|
+
|
|
581
|
+
// 2. Agent workflow
|
|
582
|
+
const agent = new AutonomousAgent({ baseUrl: "https://store.example.com" });
|
|
583
|
+
const result = await agent.autonomousShop("macbook pro", 3000);
|
|
584
|
+
|
|
585
|
+
if (result.success) {
|
|
586
|
+
console.log(`Purchased: ${result.product.label}`);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// 3. Subscribe to order updates
|
|
590
|
+
client.streamUpdates("/api/amtp/stream?events=order.updated,order.shipped", (u) => {
|
|
591
|
+
if (u.type === "order.shipped") {
|
|
592
|
+
console.log(`📦 Tracking: ${u.data.trackingNumber}`);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### Use Case 2: SaaS Dashboard Agent
|
|
598
|
+
|
|
599
|
+
```typescript
|
|
600
|
+
// 1. Server: src/server/examples/saas-dashboard-server.ts
|
|
601
|
+
// 2. Agent logs in and navigates workspaces
|
|
602
|
+
|
|
603
|
+
import { AMTPClient } from "./src/client/amtp-client";
|
|
604
|
+
|
|
605
|
+
const client = new AMTPClient({
|
|
606
|
+
baseUrl: "http://localhost:3000",
|
|
607
|
+
capabilities: ["actions", "forms"],
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
// Login
|
|
611
|
+
const { token } = await client.login("demo@example.com", "demo-password");
|
|
612
|
+
|
|
613
|
+
// Fetch AMTP workspace list
|
|
614
|
+
const workspaces = await client.getPage("/amtp/workspaces");
|
|
615
|
+
console.log(workspaces.title); // "Workspaces"
|
|
616
|
+
console.log(workspaces.links); // links to each workspace
|
|
617
|
+
|
|
618
|
+
// Navigate into first workspace
|
|
619
|
+
if (workspaces.links.length > 0) {
|
|
620
|
+
const ws = await client.clickLink(workspaces.links[0].url);
|
|
621
|
+
console.log(ws.title);
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### Use Case 3: Content Crawler + Search Engine
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
// Crawl an AMTP-powered documentation site
|
|
629
|
+
const crawler = new AMTPCrawler({
|
|
630
|
+
baseUrl: "https://docs.example.com",
|
|
631
|
+
maxPages: 5000,
|
|
632
|
+
maxDepth: 10,
|
|
633
|
+
respectRobotsTxt: true,
|
|
634
|
+
indexCallback: async (doc) => {
|
|
635
|
+
// Index each page to an external search engine
|
|
636
|
+
await fetch("https://search.example.com/index", {
|
|
637
|
+
method: "POST",
|
|
638
|
+
body: JSON.stringify({
|
|
639
|
+
url: doc.path,
|
|
640
|
+
title: doc.title,
|
|
641
|
+
content: doc.nodes.map(n => n.content).join(" "),
|
|
642
|
+
actions: doc.actions.map(a => a.id),
|
|
643
|
+
}),
|
|
644
|
+
});
|
|
645
|
+
},
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
await crawler.crawl();
|
|
649
|
+
console.log(crawler.search("authentication"));
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
## 8. Performance Benchmarks
|
|
655
|
+
|
|
656
|
+
```bash
|
|
657
|
+
npm run bench
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
Measures:
|
|
661
|
+
- Token efficiency (AMTP vs HTML — typically ~90% fewer tokens)
|
|
662
|
+
- Parser throughput (ops/sec)
|
|
663
|
+
- Large document scaling (10 to 100k sections)
|
|
664
|
+
- Memory growth after 20,000 parse operations
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
## 9. Validation & Testing
|
|
669
|
+
|
|
670
|
+
```bash
|
|
671
|
+
# Full validation
|
|
672
|
+
npm run validate # type-check + build + test
|
|
673
|
+
|
|
674
|
+
# Type check only
|
|
675
|
+
npm run type-check
|
|
676
|
+
|
|
677
|
+
# Run tests with coverage
|
|
678
|
+
npm run test:coverage
|
|
679
|
+
|
|
680
|
+
# Lint
|
|
681
|
+
npm run lint
|
|
682
|
+
|
|
683
|
+
# Security audit
|
|
684
|
+
npm run security-audit
|
|
685
|
+
|
|
686
|
+
# Validate markdown files against AMTP grammar
|
|
687
|
+
npm run amtp -- validate spec/examples/*.md
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
## 10. Docker
|
|
693
|
+
|
|
694
|
+
```bash
|
|
695
|
+
# Production build
|
|
696
|
+
docker compose up amtp-prod
|
|
697
|
+
|
|
698
|
+
# Development with hot-reload
|
|
699
|
+
docker compose up amtp-dev
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
---
|
|
703
|
+
|
|
704
|
+
## 11. Project Scripts Reference
|
|
705
|
+
|
|
706
|
+
| npm script | Command | Description |
|
|
707
|
+
|---|---|---|
|
|
708
|
+
| `npm run build` | `tsc` | Compile TypeScript to `dist/` |
|
|
709
|
+
| `npm run dev` | `tsc --watch` | Watch mode compilation |
|
|
710
|
+
| `npm run test` | `jest` | Run all tests |
|
|
711
|
+
| `npm run test:coverage` | `jest --coverage` | Tests with coverage report |
|
|
712
|
+
| `npm run lint` | `eslint src/**/*.ts` | Lint all source |
|
|
713
|
+
| `npm run type-check` | `tsc --noEmit` | TypeScript type checking |
|
|
714
|
+
| `npm run server` | `ts-node src/server/examples/basic-server.ts` | Start basic demo server |
|
|
715
|
+
| `npm run server:saas` | `ts-node src/server/examples/saas-dashboard-server.ts` | Start SaaS dashboard |
|
|
716
|
+
| `npm run client` | `ts-node src/client/examples/basic-client.ts` | Run client example |
|
|
717
|
+
| `npm run crawler` | `ts-node src/crawler/examples/basic-crawler.ts` | Run crawler example |
|
|
718
|
+
| `npm run bench` | `ts-node bench/bench.ts` | Run performance benchmarks |
|
|
719
|
+
| `npm run amtp -- <cmd>` | `ts-node bin/amtp.ts <cmd>` | CLI tool |
|
|
720
|
+
| `npm run docs` | `typedoc --out docs src/` | Generate API docs |
|
|
721
|
+
| `npm run validate` | (type-check + build + test) | Full validation |
|
|
722
|
+
| `npm run security-audit` | `npm audit` | Dependency audit |
|