@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/bin/amtp.ts
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AMTP CLI — amtp
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* amtp init Scaffold a new AMTP project in a directory
|
|
7
|
+
* amtp serve Start an AMTP development server
|
|
8
|
+
* amtp crawl Crawl a URL, print the crawl index as JSON
|
|
9
|
+
* amtp validate Check one or more markdown files for AMTP validity
|
|
10
|
+
* amtp doctor Run all local health checks (type-check, build, test, audit)
|
|
11
|
+
*
|
|
12
|
+
* Requires:
|
|
13
|
+
* npm install --save-dev commander chalk
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* node bin/amtp.ts init my-app
|
|
17
|
+
* node bin/amtp.ts serve -p 3000
|
|
18
|
+
* node bin/amtp.ts crawl https://example.com --max-pages 20
|
|
19
|
+
* node bin/amtp.ts validate README.md docs/ARCHITECTURE.md
|
|
20
|
+
* node bin/amtp.ts doctor
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { Command } from "commander";
|
|
24
|
+
import chalk from "chalk";
|
|
25
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
26
|
+
import { join, extname } from "path";
|
|
27
|
+
|
|
28
|
+
import { AMTPMarkdownParser } from "../src/server/markdown-parser";
|
|
29
|
+
import { AMTPResponseBuilder } from "../src/server/amtp-server";
|
|
30
|
+
import { SessionManager } from "../src/server/amtp-server";
|
|
31
|
+
import { SecurityError } from "../src/server/security";
|
|
32
|
+
|
|
33
|
+
/* ================================================================
|
|
34
|
+
HELPERS
|
|
35
|
+
================================================================ */
|
|
36
|
+
|
|
37
|
+
function logOk(msg: string) { console.log(chalk.green(`✅ ${msg}`)); }
|
|
38
|
+
function logWarn(msg: string) { console.log(chalk.yellow(`⚠️ ${msg}`)); }
|
|
39
|
+
function logErr(msg: string) { console.log(chalk.red(`❌ ${msg}`)); }
|
|
40
|
+
function logInfo(msg: string) { console.log(chalk.cyan(`ℹ️ ${msg}`)); }
|
|
41
|
+
|
|
42
|
+
/** Exit with a friendly error message */
|
|
43
|
+
function fatal(msg: string, code = 1): never {
|
|
44
|
+
logErr(msg);
|
|
45
|
+
process.exit(code);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Check that a file extension is Markdown (.md or .mdx) */
|
|
49
|
+
function isMarkdownFile(path: string): boolean {
|
|
50
|
+
const exts = [".md", ".mdx"];
|
|
51
|
+
return exts.includes(extname(path).toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Recursively collect .md files from provided paths */
|
|
55
|
+
function collectMarkdownFiles(paths: string[]): string[] {
|
|
56
|
+
const files: string[] = [];
|
|
57
|
+
for (const p of paths) {
|
|
58
|
+
if (!existsSync(p)) { logWarn(`File not found: ${p}`); continue; }
|
|
59
|
+
const st = require("fs").statSync(p);
|
|
60
|
+
if (st.isDirectory()) {
|
|
61
|
+
for (const entry of require("fs").readdirSync(p, { withFileTypes: true })) {
|
|
62
|
+
if (entry.isFile()) files.push(join(p, entry.name));
|
|
63
|
+
}
|
|
64
|
+
} else if (isMarkdownFile(p)) {
|
|
65
|
+
files.push(p);
|
|
66
|
+
} else {
|
|
67
|
+
logWarn(`Skipping non-markdown file: ${p}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return files;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Validate a single markdown file against AMTP grammar rules */
|
|
74
|
+
function validateMarkdownFile(path: string): { valid: boolean; errors: string[] } {
|
|
75
|
+
const errors: string[] = [];
|
|
76
|
+
const raw = readFileSync(path, "utf-8");
|
|
77
|
+
const parser = new AMTPMarkdownParser();
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const doc = parser.parse(raw, path);
|
|
81
|
+
|
|
82
|
+
if (!doc.title || doc.title === "Untitled") {
|
|
83
|
+
errors.push(`${path}: missing or empty H1 title`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check action IDs are uppercase snake_case
|
|
87
|
+
for (const a of doc.actions) {
|
|
88
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(a.label || a.id)) {
|
|
89
|
+
errors.push(`${path}: action "${a.label}" should be UPPER_SNAKE_CASE`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check for forms: ACTION / METHOD / ENDPOINT must each be present
|
|
94
|
+
for (const f of doc.forms) {
|
|
95
|
+
if (!f.action) errors.push(`${path}: form "${f.id}" missing ACTION`);
|
|
96
|
+
if (!f.method) errors.push(`${path}: form "${f.id}" missing METHOD`);
|
|
97
|
+
if (!f.endpoint) errors.push(`${path}: form "${f.id}" missing ENDDPOINT`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { valid: errors.length === 0, errors };
|
|
101
|
+
} catch (e: any) {
|
|
102
|
+
return { valid: false, errors: [`${path}: parse error — ${e.message}`] };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ================================================================
|
|
107
|
+
COMMAND: init
|
|
108
|
+
================================================================ */
|
|
109
|
+
|
|
110
|
+
function cmdInit(dir: string, options: { yes?: boolean }) {
|
|
111
|
+
const target = join(process.cwd(), dir);
|
|
112
|
+
|
|
113
|
+
if (existsSync(target)) {
|
|
114
|
+
fatal(`Directory already exists: ${target}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
logInfo(`Scaffolding AMTP project in ${target}`);
|
|
118
|
+
|
|
119
|
+
mkdirSync(target, { recursive: true });
|
|
120
|
+
mkdirSync(join(target, "src"), { recursive: true });
|
|
121
|
+
|
|
122
|
+
const files: Record<string, string> = {
|
|
123
|
+
"package.json": JSON.stringify(
|
|
124
|
+
{
|
|
125
|
+
name: `amtp-app`,
|
|
126
|
+
version: "0.1.0",
|
|
127
|
+
description: "AMTP application",
|
|
128
|
+
main: "src/server/index.ts",
|
|
129
|
+
scripts: {
|
|
130
|
+
dev: "ts-node src/server/index.ts",
|
|
131
|
+
build: "tsc",
|
|
132
|
+
test: "jest",
|
|
133
|
+
},
|
|
134
|
+
dependencies: {
|
|
135
|
+
express: "^4.18.2",
|
|
136
|
+
compression: "^1.7.4",
|
|
137
|
+
cors: "^2.8.5",
|
|
138
|
+
},
|
|
139
|
+
devDependencies: {
|
|
140
|
+
typescript: "^5.0.0",
|
|
141
|
+
tsnode: "^10.9.1",
|
|
142
|
+
jest: "^29.5.0",
|
|
143
|
+
"ts-jest": "^29.1.0",
|
|
144
|
+
"@types/express": "^4.17.17",
|
|
145
|
+
"@types/node": "^20.0.0",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
null,
|
|
149
|
+
2
|
|
150
|
+
),
|
|
151
|
+
"tsconfig.json": JSON.stringify(
|
|
152
|
+
{
|
|
153
|
+
target: "ES2020",
|
|
154
|
+
module: "commonjs",
|
|
155
|
+
rootDir: ".",
|
|
156
|
+
outDir: "dist",
|
|
157
|
+
strict: true,
|
|
158
|
+
esModuleInterop: true,
|
|
159
|
+
skipLibCheck: true,
|
|
160
|
+
},
|
|
161
|
+
null,
|
|
162
|
+
2
|
|
163
|
+
),
|
|
164
|
+
"README.md": `# AMTP App\n\nDescribe your application here.\n`,
|
|
165
|
+
"src/server/index.ts": `import express from "express";\nimport { AMTPServer } from "amtp";\n\nconst app = express();\n\nconst server = new AMTPServer({ port: 3000 });\nawait server.start();\nconsole.log("AMTP server running on :3000");\n`,
|
|
166
|
+
".gitignore": `\nnode_modules\ndist\n.env\n*.log\n`,
|
|
167
|
+
".env.example": `# AMTP Server Environment\nAMTP_PORT=3000\nAMTP_ENABLE_CORS=true\nAMTP_ENABLE_RATE_LIMIT=true\n`,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
for (const [path, content] of Object.entries(files)) {
|
|
171
|
+
const full = join(target, path);
|
|
172
|
+
mkdirSync(require("path").dirname(full), { recursive: true });
|
|
173
|
+
writeFileSync(full, content);
|
|
174
|
+
logOk(`Written: ${path}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
logOk(`Project created at ${target}`);
|
|
178
|
+
logInfo("Next: cd into the directory and run npm install");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* ================================================================
|
|
182
|
+
COMMAND: serve
|
|
183
|
+
================================================================ */
|
|
184
|
+
|
|
185
|
+
function cmdServe(port: number, host: string) {
|
|
186
|
+
logInfo(`Starting AMTP dev server on http://${host}:${port}`);
|
|
187
|
+
// Dynamically require so the compiled output can hot-reload during dev
|
|
188
|
+
const { AMTPServer } = require("../src/server/amtp-server") as any;
|
|
189
|
+
const { AMTPRequestParser } = require("../src/server/amtp-server") as any;
|
|
190
|
+
const { AMTPResponseBuilder } = require("../src/server/amtp-server") as any;
|
|
191
|
+
|
|
192
|
+
const server = new AMTPServer({ port, host, enableCORS: true, enableRateLimit: true });
|
|
193
|
+
(server as any).register("get", "/", (_req: any, res: any) => {
|
|
194
|
+
const doc = new AMTPResponseBuilder().build("# AMTP Server\n\nRunning OK");
|
|
195
|
+
const { headers, body } = doc as any;
|
|
196
|
+
for (const [k, v] of Object.entries(headers)) { if (v) res.setHeader(k, v); }
|
|
197
|
+
res.send(body);
|
|
198
|
+
});
|
|
199
|
+
(server as any).start().catch((e: Error) => fatal(e.message));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* ================================================================
|
|
203
|
+
COMMAND: crawl
|
|
204
|
+
================================================================ */
|
|
205
|
+
|
|
206
|
+
function cmdCrawl(baseUrl: string, opts: { maxPages?: number; maxDepth?: number; print?: boolean }) {
|
|
207
|
+
const { AMTPCrawler } = require("../src/crawler/amtp-crawler") as any;
|
|
208
|
+
|
|
209
|
+
const crawler = new AMTPCrawler({
|
|
210
|
+
baseUrl,
|
|
211
|
+
maxPages: opts.maxPages ?? 50,
|
|
212
|
+
maxDepth: opts.maxDepth ?? 3,
|
|
213
|
+
respectRobotsTxt: false,
|
|
214
|
+
delays: { betweenRequests: 100, betweenDomains: 500 },
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
logInfo(`Crawling ${baseUrl} (max ${opts.maxPages ?? 50} pages)…`);
|
|
218
|
+
crawler.crawl().then((pages: any[]) => {
|
|
219
|
+
const indexJson = (crawler as any).exportIndex?.() ?? JSON.stringify({ pages }, null, 2);
|
|
220
|
+
if (opts.print) { console.log(indexJson); } else { console.log(indexJson); }
|
|
221
|
+
logOk(`Crawl complete — ${pages.length} pages`);
|
|
222
|
+
}).catch((e: Error) => fatal(e.message));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* ================================================================
|
|
226
|
+
COMMAND: validate
|
|
227
|
+
================================================================ */
|
|
228
|
+
|
|
229
|
+
function cmdValidate(paths: string[], _options: { json?: boolean }) {
|
|
230
|
+
const files = collectMarkdownFiles(paths);
|
|
231
|
+
if (files.length === 0) fatal("No markdown files found to validate.");
|
|
232
|
+
|
|
233
|
+
logInfo(`Validating ${files.length} file(s)…`);
|
|
234
|
+
let pass = 0, fail = 0;
|
|
235
|
+
for (const file of files) {
|
|
236
|
+
const { valid, errors } = validateMarkdownFile(file);
|
|
237
|
+
if (valid) {
|
|
238
|
+
logOk(file);
|
|
239
|
+
pass++;
|
|
240
|
+
} else {
|
|
241
|
+
for (const e of errors) logErr(e);
|
|
242
|
+
fail++;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
console.log(`\n${pass} passed, ${fail} failed.`);
|
|
247
|
+
if (fail > 0) process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/* ================================================================
|
|
251
|
+
COMMAND: doctor
|
|
252
|
+
================================================================ */
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Run a series of health checks and print a report.
|
|
256
|
+
*/
|
|
257
|
+
async function cmdDoctor(_opts: { fix?: boolean }) {
|
|
258
|
+
const { execSync } = require("child_process");
|
|
259
|
+
let passCount = 0, warnCount = 0, failCount = 0;
|
|
260
|
+
const results: { name: string; status: string; detail?: string }[] = [];
|
|
261
|
+
|
|
262
|
+
function check(name: string, fn: () => { ok: boolean; detail?: string }) {
|
|
263
|
+
try {
|
|
264
|
+
const r = fn();
|
|
265
|
+
if (r.ok) { passCount++; results.push({ name, status: "PASS", detail: r.detail }); }
|
|
266
|
+
else { failCount++; results.push({ name, status: "FAIL", detail: r.detail }); }
|
|
267
|
+
} catch (e: any) {
|
|
268
|
+
warnCount++;
|
|
269
|
+
results.push({ name, status: "WARN", detail: e.message });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
check("Type-check", () => {
|
|
274
|
+
try { execSync("npm run type-check", { stdio: "pipe", cwd: process.cwd() }); return { ok: true }; }
|
|
275
|
+
catch { return { ok: false, detail: "tsc reported type errors" }; }
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
check("Build", () => {
|
|
279
|
+
try { execSync("npm run build", { stdio: "pipe", cwd: process.cwd() }); return { ok: true }; }
|
|
280
|
+
catch { return { ok: false, detail: "tsc build failed" }; }
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
check("Lint", () => {
|
|
284
|
+
try { execSync("npm run lint >/dev/null 2>&1", { stdio: "pipe", cwd: process.cwd() }); return { ok: true }; }
|
|
285
|
+
catch { return { ok: false, detail: "eslint reported warnings" }; }
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
check("Tests", () => {
|
|
289
|
+
try {
|
|
290
|
+
const out = execSync("npm test 2>&1", { stdio: "pipe", cwd: process.cwd() });
|
|
291
|
+
const m = out.toString();
|
|
292
|
+
return { ok: m.includes("PASS"), detail: m.split("\n").pop()?.trim() };
|
|
293
|
+
} catch { return { ok: false, detail: "jest reported failures" }; }
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
check("npm audit (>= high)", () => {
|
|
297
|
+
try {
|
|
298
|
+
const out = execSync("npm audit --audit-level=moderate 2>&1", { stdio: "pipe", cwd: process.cwd() });
|
|
299
|
+
return { ok: true, detail: "No high/critical vulns" };
|
|
300
|
+
} catch { return { ok: false, detail: "npm audit found issues" }; }
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
check("Node version", () => {
|
|
304
|
+
const v = parseInt(process.versions.node.split(".")[0]);
|
|
305
|
+
return { ok: v >= 18, detail: `Node ${process.versions.node}`, };
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
check("env file", () => {
|
|
309
|
+
return { ok: !existsSync(join(process.cwd(), ".env")), detail: existsSync(join(process.cwd(), ".env")) ? ".env exists — verify secrets are .gitignore'd" : "OK" };
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
check("gitignore", () => {
|
|
313
|
+
return { ok: existsSync(join(process.cwd(), ".gitignore")), detail: ".gitignore present" };
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
check("AMTP server types", () => {
|
|
317
|
+
try { require("../src/server/amtp-server"); return { ok: true }; }
|
|
318
|
+
catch (e: any) { return { ok: false, detail: e.message }; }
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
// ─── report ───────────────────────────────────────────────────────────
|
|
322
|
+
console.log("\n┌──────────────────────────────────────┐");
|
|
323
|
+
console.log("│ amtp doctor │");
|
|
324
|
+
console.log("├──────────────┬─────────────────────────┤");
|
|
325
|
+
for (const r of results) {
|
|
326
|
+
const symbol = r.status === "PASS" ? "✓" : r.status === "WARN" ? "△" : "✗";
|
|
327
|
+
const color = r.status === "PASS" ? chalk.green : r.status === "WARN" ? chalk.yellow : chalk.red;
|
|
328
|
+
console.log(`│ ${color(symbol)} ${r.name.slice(0, 12).padEnd(12)}│${color(r.status).padEnd(26)}│`);
|
|
329
|
+
if (r.detail) console.log(`│ │${chalk.gray(r.detail).padEnd(26)}│`);
|
|
330
|
+
}
|
|
331
|
+
console.log("└──────────────┴─────────────────────────┘");
|
|
332
|
+
const total = passCount + warnCount + failCount;
|
|
333
|
+
console.log(`\n ${chalk.green(passCount + "/" + total + " passed")} ${warnCount > 0 ? chalk.yellow(warnCount + " warnings") : ""} ${failCount > 0 ? chalk.red(failCount + " failed") : ""}`);
|
|
334
|
+
|
|
335
|
+
process.exit(failCount > 0 ? 1 : 0);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* ================================================================
|
|
339
|
+
CLI
|
|
340
|
+
================================================================ */
|
|
341
|
+
|
|
342
|
+
const program = new Command();
|
|
343
|
+
|
|
344
|
+
program
|
|
345
|
+
.name("amtp")
|
|
346
|
+
.description("AMTP: Agent Markdown Transfer Protocol — CLI toolkit")
|
|
347
|
+
.version("1.0.0");
|
|
348
|
+
|
|
349
|
+
program
|
|
350
|
+
.command("init")
|
|
351
|
+
.description("Scaffold a new AMTP project in a directory")
|
|
352
|
+
.argument("<dir>", "Directory to create")
|
|
353
|
+
.option("--yes", "Non-interactive, skip all prompts", false)
|
|
354
|
+
.action((dir: string, opts: any) => cmdInit(dir, opts));
|
|
355
|
+
|
|
356
|
+
program
|
|
357
|
+
.command("serve")
|
|
358
|
+
.description("Start a local AMTP development server")
|
|
359
|
+
.option("-p, --port <port>", "Port to listen on", "3000")
|
|
360
|
+
.option("-h, --host <host>", "Host to bind on", "0.0.0.0")
|
|
361
|
+
.action((opts: any) => void cmdServe(parseInt(opts.port), opts.host));
|
|
362
|
+
|
|
363
|
+
program
|
|
364
|
+
.command("crawl")
|
|
365
|
+
.description("Crawl an AMTP-enabled site and dump the crawl index as JSON")
|
|
366
|
+
.argument("<url>", "Base URL of the AMTP site to crawl")
|
|
367
|
+
.option("--max-pages <n>", "Max pages to crawl", "50")
|
|
368
|
+
.option("--max-depth <n>", "Max crawl depth", "3")
|
|
369
|
+
.option("--print", "Print the index JSON to stdout", true)
|
|
370
|
+
.action((url: string, opts: any) => {
|
|
371
|
+
void cmdCrawl(url, { maxPages: parseInt(opts.maxPages), maxDepth: parseInt(opts.maxDepth), print: opts.print });
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
program
|
|
375
|
+
.command("validate")
|
|
376
|
+
.description("Check markdown files for AMTP structural validity")
|
|
377
|
+
.argument("<files...>", "Markdown file(s) or directory to validate")
|
|
378
|
+
.option("--json", "Output results as JSON", false)
|
|
379
|
+
.action((files: string[], opts: any) => cmdValidate(files, opts));
|
|
380
|
+
|
|
381
|
+
program
|
|
382
|
+
.command("doctor")
|
|
383
|
+
.description("Run all local health checks and report results")
|
|
384
|
+
.option("--fix", "Attempt automatic fixes where supported", false)
|
|
385
|
+
.action((opts: any) => void cmdDoctor(opts));
|
|
386
|
+
|
|
387
|
+
program.parse();
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AMTP Agent Client SDK
|
|
3
|
+
* TypeScript SDK for AI agents to interact with AMTP servers
|
|
4
|
+
*/
|
|
5
|
+
/// <reference types="node" />
|
|
6
|
+
import { AMTPDocument, Action, Form, StreamUpdate, MarkdownNode, AMTPBatchRequest, AMTPBatchResponse, AMTPQLResult } from "../types/amtp.types";
|
|
7
|
+
/**
|
|
8
|
+
* AMTP Client Configuration
|
|
9
|
+
*/
|
|
10
|
+
export interface AMTPClientConfig {
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
sessionId?: string;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
maxRetries?: number;
|
|
15
|
+
capabilities?: string[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* AMTP Agent Client
|
|
19
|
+
* Main client for agents to interact with AMTP servers
|
|
20
|
+
*/
|
|
21
|
+
export declare class AMTPClient {
|
|
22
|
+
private baseUrl;
|
|
23
|
+
private sessionId?;
|
|
24
|
+
private timeout;
|
|
25
|
+
private maxRetries;
|
|
26
|
+
private capabilities;
|
|
27
|
+
constructor(config: AMTPClientConfig);
|
|
28
|
+
/**
|
|
29
|
+
* Set session ID
|
|
30
|
+
*/
|
|
31
|
+
setSessionId(sessionId: string): void;
|
|
32
|
+
/**
|
|
33
|
+
* Get session ID
|
|
34
|
+
*/
|
|
35
|
+
getSessionId(): string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Fetch a page (supports cursor-based pagination)
|
|
38
|
+
*/
|
|
39
|
+
getPage(path: string, options?: {
|
|
40
|
+
cursor?: string;
|
|
41
|
+
}): Promise<AMTPDocument>;
|
|
42
|
+
/**
|
|
43
|
+
* Convenience method: fetch the next page using the pagination info from the current document.
|
|
44
|
+
* Returns null if there is no next page/cursor.
|
|
45
|
+
*/
|
|
46
|
+
getNextPage(currentDoc: AMTPDocument): Promise<AMTPDocument | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Extract all media nodes (images, video, audio) from a document.
|
|
49
|
+
* These now include rich `description` from amtp-meta blocks for agents.
|
|
50
|
+
*/
|
|
51
|
+
getMedia(doc: AMTPDocument): MarkdownNode[];
|
|
52
|
+
/**
|
|
53
|
+
* Execute an action
|
|
54
|
+
*/
|
|
55
|
+
executeAction(action: string, parameters?: Record<string, unknown>): Promise<any>;
|
|
56
|
+
/**
|
|
57
|
+
* Execute multiple actions in a single batch request (v1.1).
|
|
58
|
+
* See AMTPBatchRequest for options (atomic, continueOnError, etc.).
|
|
59
|
+
*/
|
|
60
|
+
executeBatch(actions: Array<{
|
|
61
|
+
action: string;
|
|
62
|
+
parameters?: Record<string, unknown>;
|
|
63
|
+
}>, options?: AMTPBatchRequest['options']): Promise<AMTPBatchResponse>;
|
|
64
|
+
/**
|
|
65
|
+
* Execute an AMTP-QL query (v2.0) against the current document context.
|
|
66
|
+
* Returns the projected result (much smaller than full document).
|
|
67
|
+
*/
|
|
68
|
+
query(rawQuery: string, variables?: Record<string, unknown>): Promise<AMTPQLResult>;
|
|
69
|
+
/**
|
|
70
|
+
* Register a webhook for push notifications (v2.0)
|
|
71
|
+
*/
|
|
72
|
+
registerWebhook(payload: {
|
|
73
|
+
url: string;
|
|
74
|
+
events: string[];
|
|
75
|
+
secret?: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
}): Promise<any>;
|
|
78
|
+
/**
|
|
79
|
+
* List registered webhooks
|
|
80
|
+
*/
|
|
81
|
+
listWebhooks(): Promise<any>;
|
|
82
|
+
/**
|
|
83
|
+
* Submit a form
|
|
84
|
+
*/
|
|
85
|
+
submitForm(form: Form, values: Record<string, unknown>): Promise<any>;
|
|
86
|
+
/**
|
|
87
|
+
* Navigate to a URL
|
|
88
|
+
*/
|
|
89
|
+
navigate(path: string): Promise<AMTPDocument>;
|
|
90
|
+
/**
|
|
91
|
+
* Click a link
|
|
92
|
+
*/
|
|
93
|
+
clickLink(url: string): Promise<AMTPDocument>;
|
|
94
|
+
/**
|
|
95
|
+
* Login
|
|
96
|
+
*/
|
|
97
|
+
login(username: string, password: string): Promise<{
|
|
98
|
+
sessionId: string;
|
|
99
|
+
userId: string;
|
|
100
|
+
}>;
|
|
101
|
+
/**
|
|
102
|
+
* Logout
|
|
103
|
+
*/
|
|
104
|
+
logout(): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Stream updates from server
|
|
107
|
+
*/
|
|
108
|
+
streamUpdates(path: string, callback: (update: StreamUpdate) => void): EventSource;
|
|
109
|
+
/**
|
|
110
|
+
* Get available actions on a page
|
|
111
|
+
*/
|
|
112
|
+
getActions(path: string): Promise<Action[]>;
|
|
113
|
+
/**
|
|
114
|
+
* Get available forms on a page
|
|
115
|
+
*/
|
|
116
|
+
getForms(path: string): Promise<Form[]>;
|
|
117
|
+
/**
|
|
118
|
+
* Search page content
|
|
119
|
+
*/
|
|
120
|
+
search(query: string, limit?: number): Promise<AMTPDocument>;
|
|
121
|
+
/**
|
|
122
|
+
* Build request headers
|
|
123
|
+
*/
|
|
124
|
+
private buildHeaders;
|
|
125
|
+
/**
|
|
126
|
+
* Fetch with retry logic
|
|
127
|
+
*/
|
|
128
|
+
private fetchWithRetry;
|
|
129
|
+
/**
|
|
130
|
+
* Generate unique request ID
|
|
131
|
+
*/
|
|
132
|
+
private generateRequestId;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Simple Markdown Parser for Client
|
|
136
|
+
*/
|
|
137
|
+
export declare class AMTPMarkdownParser {
|
|
138
|
+
parse(markdown: string, path: string): AMTPDocument;
|
|
139
|
+
private extractActions;
|
|
140
|
+
private extractForms;
|
|
141
|
+
private extractLinks;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Autonomous Agent Workflow
|
|
145
|
+
* Example of building autonomous workflows
|
|
146
|
+
*/
|
|
147
|
+
export declare class AutonomousAgent {
|
|
148
|
+
private client;
|
|
149
|
+
constructor(config: AMTPClientConfig);
|
|
150
|
+
/**
|
|
151
|
+
* Example: Autonomous shopping workflow
|
|
152
|
+
*/
|
|
153
|
+
autonomousShop(productQuery: string, maxPrice: number): Promise<any>;
|
|
154
|
+
/**
|
|
155
|
+
* Example: Multi-step authenticated workflow
|
|
156
|
+
*/
|
|
157
|
+
authenticatedWorkflow(username: string, password: string): Promise<any>;
|
|
158
|
+
}
|
|
159
|
+
declare const _default: {
|
|
160
|
+
AMTPClient: typeof AMTPClient;
|
|
161
|
+
AMTPMarkdownParser: typeof AMTPMarkdownParser;
|
|
162
|
+
AutonomousAgent: typeof AutonomousAgent;
|
|
163
|
+
};
|
|
164
|
+
export default _default;
|