@7h3/protocol 0.5.2 → 0.5.4

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/bin/7h3.js ADDED
@@ -0,0 +1,663 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'node:util';
3
+ import { createServer } from 'node:http';
4
+ import { writeFileSync } from 'node:fs';
5
+ const USAGE = `
6
+ 7h3 — Protocol CLI (wire version 7h3/0.1)
7
+
8
+ Usage:
9
+ 7h3 keygen [--output <file>]
10
+ 7h3 sign --private-key <key> --sender <id> [--recipient <id>] [--payload <str>] [--ttl <ms>]
11
+ 7h3 verify --public-key <key> --envelope <json>
12
+ 7h3 inspect --envelope <json>
13
+ 7h3 gateway --upstream <url> [--port <n>] [--public-key <key>] [--require ed25519|none]
14
+ [--sign-responses] [--private-key <key>] [--sender <id>] [--metrics-port <n>]
15
+ 7h3 keys serve [--public-key <key>] [--key-id <id>] [--port <n>]
16
+ 7h3 add --framework <name> [--sender <id>] [--output <dir>]
17
+ 7h3 help
18
+
19
+ Commands:
20
+ keygen Generate an Ed25519 keypair (PKCS8/SPKI, base64url-encoded)
21
+ sign Create and sign a 7h3 envelope
22
+ verify Verify a 7h3 envelope signature
23
+ inspect Pretty-print a 7h3 envelope fields
24
+ gateway Run a verifying HTTP proxy gateway
25
+ keys serve Serve a /.well-known/7h3-keys endpoint
26
+ add Scaffold 7h3 integration into a project (see --framework options)
27
+ help Show this usage table
28
+ `;
29
+ function die(msg) {
30
+ process.stderr.write(`Error: ${msg}\n`);
31
+ process.exit(1);
32
+ }
33
+ async function cmdKeygen(argv) {
34
+ const { values } = parseArgs({
35
+ args: argv,
36
+ options: {
37
+ output: { type: 'string', short: 'o' },
38
+ },
39
+ strict: false,
40
+ });
41
+ const { generateEd25519KeypairBase64Url } = await import('@7h3/protocol');
42
+ const { publicKey, privateKey } = await generateEd25519KeypairBase64Url();
43
+ const result = {
44
+ algorithm: 'Ed25519',
45
+ wireVersion: '7h3/0.1',
46
+ publicKey,
47
+ privateKey,
48
+ createdAt: new Date().toISOString(),
49
+ warning: 'Keep privateKey secret — it grants signing authority over your agent identity.',
50
+ };
51
+ const json = JSON.stringify(result, null, 2);
52
+ if (values.output) {
53
+ writeFileSync(values.output, json, 'utf8');
54
+ process.stdout.write(`Keypair written to ${values.output}\n`);
55
+ process.stdout.write(` algorithm : Ed25519\n`);
56
+ process.stdout.write(` publicKey : ${publicKey}\n`);
57
+ process.stdout.write(` createdAt : ${result.createdAt}\n`);
58
+ }
59
+ else {
60
+ process.stdout.write(json + '\n');
61
+ }
62
+ }
63
+ async function cmdSign(argv) {
64
+ const { values } = parseArgs({
65
+ args: argv,
66
+ options: {
67
+ 'private-key': { type: 'string' },
68
+ sender: { type: 'string' },
69
+ recipient: { type: 'string' },
70
+ payload: { type: 'string' },
71
+ ttl: { type: 'string' },
72
+ },
73
+ strict: false,
74
+ });
75
+ const privateKey = values['private-key'];
76
+ const sender = values['sender'];
77
+ if (!privateKey)
78
+ die('--private-key is required');
79
+ if (!sender)
80
+ die('--sender is required');
81
+ const { createEnvelope, signEnvelopeEd25519 } = await import('@7h3/protocol');
82
+ const envelope = createEnvelope({
83
+ sender: sender,
84
+ recipient: values['recipient'],
85
+ intent: 'PING',
86
+ content: values['payload'] ?? '',
87
+ ttlMs: values['ttl'] ? parseInt(values['ttl'], 10) : 60_000,
88
+ });
89
+ const signed = await signEnvelopeEd25519(envelope, privateKey);
90
+ process.stdout.write(JSON.stringify(signed) + '\n');
91
+ }
92
+ async function cmdVerify(argv) {
93
+ const { values } = parseArgs({
94
+ args: argv,
95
+ options: {
96
+ 'public-key': { type: 'string' },
97
+ envelope: { type: 'string' },
98
+ },
99
+ strict: false,
100
+ });
101
+ const publicKey = values['public-key'];
102
+ const envelopeJson = values['envelope'];
103
+ if (!publicKey)
104
+ die('--public-key is required');
105
+ if (!envelopeJson)
106
+ die('--envelope is required');
107
+ const { validateEnvelope, verifyEnvelopeEd25519 } = await import('@7h3/protocol');
108
+ let envelope;
109
+ try {
110
+ envelope = JSON.parse(envelopeJson);
111
+ }
112
+ catch {
113
+ die('--envelope is not valid JSON');
114
+ }
115
+ const diagnostics = validateEnvelope(envelope);
116
+ const errors = diagnostics.filter(d => d.level === 'error');
117
+ const warnings = diagnostics.filter(d => d.level === 'warning');
118
+ if (errors.length > 0) {
119
+ process.stdout.write('INVALID — envelope validation errors:\n');
120
+ for (const e of errors)
121
+ process.stdout.write(` [error] ${e.message}\n`);
122
+ for (const w of warnings)
123
+ process.stdout.write(` [warning] ${w.message}\n`);
124
+ process.exit(1);
125
+ }
126
+ const valid = await verifyEnvelopeEd25519(envelope, publicKey);
127
+ if (valid) {
128
+ process.stdout.write('Signature valid\n');
129
+ if (warnings.length > 0) {
130
+ for (const w of warnings)
131
+ process.stdout.write(` [warning] ${w.message}\n`);
132
+ }
133
+ }
134
+ else {
135
+ process.stdout.write('INVALID — signature verification failed\n');
136
+ process.stdout.write(` alg : ${envelope.signature?.alg ?? 'none'}\n`);
137
+ process.stdout.write(` keyId : ${envelope.signature?.keyId ?? 'none'}\n`);
138
+ process.exit(1);
139
+ }
140
+ }
141
+ async function cmdInspect(argv) {
142
+ const { values } = parseArgs({
143
+ args: argv,
144
+ options: {
145
+ envelope: { type: 'string' },
146
+ },
147
+ strict: false,
148
+ });
149
+ const envelopeJson = values['envelope'];
150
+ if (!envelopeJson)
151
+ die('--envelope is required');
152
+ let envelope;
153
+ try {
154
+ envelope = JSON.parse(envelopeJson);
155
+ }
156
+ catch {
157
+ die('--envelope is not valid JSON');
158
+ }
159
+ const h = envelope.header ?? {};
160
+ const b = envelope.body ?? {};
161
+ const s = envelope.signature;
162
+ const nowMs = Date.now();
163
+ const expiresMs = (h.timestampMs ?? 0) + (h.ttlMs ?? 0);
164
+ const expired = expiresMs < nowMs;
165
+ const expiresStatus = expired
166
+ ? `EXPIRED (${new Date(expiresMs).toISOString()})`
167
+ : `OK — expires ${new Date(expiresMs).toISOString()}`;
168
+ const content = typeof b.content === 'string' ? b.content : '';
169
+ const contentPreview = content.length > 80 ? content.slice(0, 77) + '...' : content;
170
+ process.stdout.write(`Wire Version : ${h.version ?? '(none)'}\n`);
171
+ process.stdout.write(`Message ID : ${h.messageId ?? '(none)'}\n`);
172
+ process.stdout.write(`Sender : ${h.sender ?? '(none)'}\n`);
173
+ process.stdout.write(`Recipient : ${h.recipient ?? '(none)'}\n`);
174
+ process.stdout.write(`Timestamp : ${h.timestampMs ? new Date(h.timestampMs).toISOString() : '(none)'}\n`);
175
+ process.stdout.write(`TTL : ${h.ttlMs ?? '(none)'} ms\n`);
176
+ process.stdout.write(`Expires : ${expiresStatus}\n`);
177
+ process.stdout.write(`Intent : ${b.intent ?? '(none)'}\n`);
178
+ process.stdout.write(`Content : ${contentPreview || '(empty)'}\n`);
179
+ if (s) {
180
+ process.stdout.write(`Sig alg : ${s.alg}\n`);
181
+ process.stdout.write(`Sig keyId : ${s.keyId}\n`);
182
+ }
183
+ else {
184
+ process.stdout.write(`Signature : (none)\n`);
185
+ }
186
+ }
187
+ async function cmdGateway(argv) {
188
+ const { values } = parseArgs({
189
+ args: argv,
190
+ options: {
191
+ upstream: { type: 'string' },
192
+ port: { type: 'string' },
193
+ 'public-key': { type: 'string' },
194
+ require: { type: 'string' },
195
+ 'sign-responses': { type: 'boolean' },
196
+ 'private-key': { type: 'string' },
197
+ sender: { type: 'string' },
198
+ 'metrics-port': { type: 'string' },
199
+ },
200
+ strict: false,
201
+ });
202
+ const upstream = values['upstream'];
203
+ if (!upstream)
204
+ die('--upstream is required');
205
+ const port = parseInt(values['port'] ?? '8080', 10);
206
+ const publicKey = values['public-key'];
207
+ const requireMode = values['require'] ?? (publicKey ? 'ed25519' : 'none');
208
+ const signResponses = !!(values['sign-responses']);
209
+ const privateKey = values['private-key'];
210
+ const sender = values['sender'];
211
+ const metricsPortRaw = values['metrics-port'];
212
+ const metricsPort = metricsPortRaw ? parseInt(metricsPortRaw, 10) : undefined;
213
+ const { createGateway } = await import('@7h3/protocol/gateway');
214
+ const { createStaticKeyRegistry } = await import('@7h3/protocol/key-registry');
215
+ const keys = {};
216
+ if (publicKey && sender)
217
+ keys[sender] = publicKey;
218
+ const keyRegistry = createStaticKeyRegistry(keys);
219
+ const gateway = createGateway({
220
+ upstream: upstream,
221
+ keyRegistry,
222
+ signResponses: signResponses && !!privateKey,
223
+ privateKey,
224
+ sender,
225
+ defaultPolicy: requireMode === 'none' ? 'allow' : 'deny',
226
+ });
227
+ const server = createServer(async (req, res) => {
228
+ const chunks = [];
229
+ req.on('data', (chunk) => chunks.push(chunk));
230
+ req.on('end', async () => {
231
+ const body = Buffer.concat(chunks);
232
+ // Flatten headers: string | string[] => string
233
+ const headers = {};
234
+ for (const [k, v] of Object.entries(req.headers)) {
235
+ if (v === undefined)
236
+ continue;
237
+ headers[k] = Array.isArray(v) ? v.join(', ') : v;
238
+ }
239
+ try {
240
+ const result = await gateway.handle({
241
+ method: req.method ?? 'GET',
242
+ path: req.url ?? '/',
243
+ headers,
244
+ body: body.length > 0 ? body.toString('utf8') : undefined,
245
+ });
246
+ res.writeHead(result.status, result.headers);
247
+ res.end(result.body);
248
+ }
249
+ catch (err) {
250
+ res.writeHead(502, { 'content-type': 'text/plain' });
251
+ res.end(`Gateway error: ${String(err)}`);
252
+ }
253
+ });
254
+ });
255
+ server.listen(port, () => {
256
+ process.stderr.write(`7h3 gateway listening on port ${port}\n`);
257
+ process.stderr.write(` upstream : ${upstream}\n`);
258
+ process.stderr.write(` verify mode : ${requireMode}\n`);
259
+ process.stderr.write(` sign-responses: ${signResponses && !!privateKey}\n`);
260
+ if (sender)
261
+ process.stderr.write(` sender : ${sender}\n`);
262
+ });
263
+ // Optional: dedicated metrics server
264
+ if (metricsPort !== undefined) {
265
+ const { metrics: globalMetrics, renderPrometheusText } = await import('@7h3/protocol/telemetry');
266
+ const metricsServer = createServer((req, res) => {
267
+ if (req.url === '/metrics' && req.method === 'GET') {
268
+ const body = renderPrometheusText(globalMetrics);
269
+ res.writeHead(200, { 'content-type': 'text/plain; version=0.0.4; charset=utf-8' });
270
+ res.end(body);
271
+ }
272
+ else {
273
+ res.writeHead(404, { 'content-type': 'text/plain' });
274
+ res.end('Not Found');
275
+ }
276
+ });
277
+ metricsServer.listen(metricsPort, () => {
278
+ process.stderr.write(`7h3 metrics listening on :${metricsPort}/metrics\n`);
279
+ });
280
+ }
281
+ }
282
+ async function cmdKeysServe(argv) {
283
+ const { values } = parseArgs({
284
+ args: argv,
285
+ options: {
286
+ 'public-key': { type: 'string' },
287
+ 'key-id': { type: 'string' },
288
+ port: { type: 'string' },
289
+ },
290
+ strict: false,
291
+ });
292
+ const publicKey = values['public-key'];
293
+ const keyId = values['key-id'] ?? 'default';
294
+ const port = parseInt(values['port'] ?? '8081', 10);
295
+ const { serveWellKnownKeys } = await import('@7h3/protocol/keys');
296
+ const doc = {
297
+ version: '7h3/0.1',
298
+ updated: Date.now(),
299
+ keys: publicKey
300
+ ? [
301
+ {
302
+ id: keyId,
303
+ algorithm: 'Ed25519',
304
+ publicKey,
305
+ created: Date.now(),
306
+ },
307
+ ]
308
+ : [],
309
+ };
310
+ const body = serveWellKnownKeys(doc);
311
+ const server = createServer((req, res) => {
312
+ if (req.url === '/.well-known/7h3-keys') {
313
+ res.writeHead(200, { 'content-type': 'application/json' });
314
+ res.end(body);
315
+ }
316
+ else {
317
+ res.writeHead(404, { 'content-type': 'text/plain' });
318
+ res.end('Not Found');
319
+ }
320
+ });
321
+ server.listen(port, () => {
322
+ process.stderr.write(`7h3 key server listening on port ${port}\n`);
323
+ process.stderr.write(` GET /.well-known/7h3-keys\n`);
324
+ if (publicKey)
325
+ process.stderr.write(` key-id: ${keyId}\n`);
326
+ else
327
+ process.stderr.write(` (no keys configured — empty document)\n`);
328
+ });
329
+ }
330
+ // ─── 7h3 add ───────────────────────────────────────────────────────────────────
331
+ const ADD_FRAMEWORKS = ['cloudflare-worker', 'nextjs', 'express', 'hono', 'fastify', 'claude-code', 'opencode', 'codex', 'grok'];
332
+ const FRAMEWORK_SNIPPETS = {
333
+ 'cloudflare-worker': (sender) => `// cloudflare/src/worker.ts — 7h3 Gateway Worker
334
+ // Install: npm install @7h3/protocol
335
+ // See: cloudflare/DEPLOY.md for full setup
336
+
337
+ import { createGateway } from '@7h3/protocol/gateway'
338
+ import { KvKeyRegistry } from './kv-key-registry'
339
+ import { KvReplayStore } from './kv-replay-store'
340
+
341
+ interface Env {
342
+ KEY_REGISTRY: KVNamespace
343
+ REPLAY_STORE: KVNamespace
344
+ UPSTREAM_URL: string
345
+ GATEWAY_PRIVATE_KEY?: string
346
+ GATEWAY_SENDER?: string
347
+ }
348
+
349
+ export default {
350
+ async fetch(request: Request, env: Env): Promise<Response> {
351
+ const gateway = createGateway({
352
+ upstream: env.UPSTREAM_URL,
353
+ keyRegistry: new KvKeyRegistry(env.KEY_REGISTRY),
354
+ replayStore: new KvReplayStore(env.REPLAY_STORE),
355
+ defaultPolicy: 'deny',
356
+ privateKey: env.GATEWAY_PRIVATE_KEY,
357
+ sender: env.GATEWAY_SENDER ?? '${sender}',
358
+ })
359
+ const url = new URL(request.url)
360
+ const headers: Record<string, string> = {}
361
+ request.headers.forEach((v, k) => { headers[k] = v })
362
+ const result = await gateway.verify({ method: request.method, path: url.pathname, headers })
363
+ if (!result.ok) return new Response(result.reason, { status: result.status })
364
+ return fetch(env.UPSTREAM_URL + url.pathname + url.search, { method: request.method, headers })
365
+ }
366
+ }
367
+ `,
368
+ 'nextjs': (sender) => `// middleware.ts — add to your Next.js project root
369
+ // Install: npm install @7h3/protocol
370
+ import { NextRequest, NextResponse } from 'next/server'
371
+ import { verifyHttpEnvelope } from '@7h3/protocol/http'
372
+ import { createStaticKeyRegistry } from '@7h3/protocol/key-registry'
373
+
374
+ // Load trusted peer public keys from env vars
375
+ const PEER_KEYS = Object.fromEntries(
376
+ (process.env.P7H3_TRUSTED_KEYS ?? '').split(',').filter(Boolean)
377
+ .map(pair => pair.split('=') as [string, string])
378
+ )
379
+
380
+ const registry = createStaticKeyRegistry(PEER_KEYS)
381
+
382
+ export async function middleware(req: NextRequest) {
383
+ const headers = Object.fromEntries(req.headers)
384
+ const result = await verifyHttpEnvelope(headers, { keyRegistry: registry })
385
+ if (!result.ok) {
386
+ return NextResponse.json({ error: result.reason }, { status: 401 })
387
+ }
388
+ const response = NextResponse.next()
389
+ response.headers.set('x-7h3-sender', (result as any).envelope?.header?.sender ?? '')
390
+ return response
391
+ }
392
+
393
+ export const config = { matcher: '/api/:path*' }
394
+
395
+ // Usage: set env var P7H3_TRUSTED_KEYS="agent@example.com=<base64url-pubkey>,..."
396
+ // Generate keypair: npx 7h3 keygen
397
+ // Self-identity: ${sender}
398
+ `,
399
+ 'express': (sender) => `// middleware/7h3-auth.ts — add to your Express project
400
+ // Install: npm install @7h3/protocol
401
+ import type { Request, Response, NextFunction } from 'express'
402
+ import { verifyHttpEnvelope } from '@7h3/protocol/http'
403
+ import { createStaticKeyRegistry } from '@7h3/protocol/key-registry'
404
+
405
+ const registry = createStaticKeyRegistry({
406
+ 'peer-agent@example.com': process.env.PEER_PUBLIC_KEY ?? '',
407
+ })
408
+
409
+ export async function verify7h3(req: Request, res: Response, next: NextFunction) {
410
+ const result = await verifyHttpEnvelope(
411
+ req.headers as Record<string, string>,
412
+ { keyRegistry: registry },
413
+ )
414
+ if (!result.ok) return res.status(401).json({ error: result.reason })
415
+ ;(req as any).sender7h3 = (result as any).envelope?.header?.sender
416
+ next()
417
+ }
418
+
419
+ // Mount in app.ts:
420
+ // import { verify7h3 } from './middleware/7h3-auth'
421
+ // app.use('/api', verify7h3)
422
+ //
423
+ // Self-identity: ${sender}
424
+ `,
425
+ 'hono': (sender) => `// middleware/7h3-auth.ts — add to your Hono project
426
+ // Install: npm install @7h3/protocol hono
427
+ import { createMiddleware } from 'hono/factory'
428
+ import { verifyHttpEnvelope } from '@7h3/protocol/http'
429
+ import { createStaticKeyRegistry } from '@7h3/protocol/key-registry'
430
+
431
+ const registry = createStaticKeyRegistry({
432
+ 'peer-agent@example.com': process.env.PEER_PUBLIC_KEY ?? '',
433
+ })
434
+
435
+ export const auth7h3 = createMiddleware(async (c, next) => {
436
+ const headers = Object.fromEntries(c.req.raw.headers)
437
+ const result = await verifyHttpEnvelope(headers, { keyRegistry: registry })
438
+ if (!result.ok) return c.json({ error: result.reason }, 401)
439
+ c.set('sender7h3', (result as any).envelope?.header?.sender ?? '')
440
+ await next()
441
+ })
442
+
443
+ // Mount in your Hono app:
444
+ // import { auth7h3 } from './middleware/7h3-auth'
445
+ // app.use('/api/*', auth7h3)
446
+ //
447
+ // Self-identity: ${sender}
448
+ `,
449
+ 'fastify': (sender) => `// plugins/7h3-auth.ts — add to your Fastify project
450
+ // Install: npm install @7h3/protocol fastify
451
+ import fp from 'fastify-plugin'
452
+ import { verifyHttpEnvelope } from '@7h3/protocol/http'
453
+ import { createStaticKeyRegistry } from '@7h3/protocol/key-registry'
454
+
455
+ const registry = createStaticKeyRegistry({
456
+ 'peer-agent@example.com': process.env.PEER_PUBLIC_KEY ?? '',
457
+ })
458
+
459
+ export default fp(async (fastify) => {
460
+ fastify.addHook('preHandler', async (request, reply) => {
461
+ const result = await verifyHttpEnvelope(
462
+ request.headers as Record<string, string>,
463
+ { keyRegistry: registry },
464
+ )
465
+ if (!result.ok) {
466
+ return reply.code(401).send({ error: result.reason })
467
+ }
468
+ request.sender7h3 = (result as any).envelope?.header?.sender ?? ''
469
+ })
470
+ })
471
+
472
+ // In your main file: fastify.register(import('./plugins/7h3-auth'))
473
+ // Self-identity: ${sender}
474
+ `,
475
+ 'claude-code': (_sender) => `# Install 7h3 Protocol MCP server in Claude Code
476
+
477
+ ## Option 1 — one command (recommended)
478
+
479
+ \`\`\`bash
480
+ claude mcp add 7h3-protocol -- npx -y @7h3/protocol-mcp
481
+ \`\`\`
482
+
483
+ ## Option 2 — project .claude/settings.json
484
+
485
+ Copy .claude/settings.example.json to .claude/settings.json:
486
+
487
+ \`\`\`json
488
+ {
489
+ "mcpServers": {
490
+ "7h3-protocol": {
491
+ "command": "npx",
492
+ "args": ["-y", "@7h3/protocol-mcp"]
493
+ }
494
+ }
495
+ }
496
+ \`\`\`
497
+
498
+ ## Available tools after install
499
+
500
+ - 7h3_generate_keypair — Ed25519 keypair
501
+ - 7h3_generate_secret — HMAC secret
502
+ - 7h3_sign — sign a test envelope
503
+ - 7h3_verify — verify an envelope
504
+ - 7h3_scaffold — generate framework integration code
505
+ - 7h3_mcp_config — get config for other editors
506
+ - 7h3_wrap_mcp_server — wrap an MCP handler with 7h3 signing
507
+ `,
508
+ 'opencode': (_sender) => `# Install 7h3 Protocol MCP server in Opencode
509
+
510
+ Add to your opencode config (~/.config/opencode/config.json or opencode.json):
511
+
512
+ \`\`\`json
513
+ {
514
+ "mcp": {
515
+ "7h3-protocol": {
516
+ "command": "npx",
517
+ "args": ["-y", "@7h3/protocol-mcp"],
518
+ "type": "stdio"
519
+ }
520
+ }
521
+ }
522
+ \`\`\`
523
+
524
+ Or one command:
525
+ \`\`\`bash
526
+ opencode mcp add 7h3-protocol npx -y @7h3/protocol-mcp
527
+ \`\`\`
528
+
529
+ After install, ask: "scaffold 7h3 for my hono project" and the AI will call 7h3_scaffold automatically.
530
+ `,
531
+ 'codex': (_sender) => `# 7h3 Protocol — GPT Codex integration
532
+
533
+ Codex reads AGENTS.md automatically. This repo has AGENTS.md with:
534
+ - Installation instructions for all SDKs
535
+ - Framework integration snippets (CF Worker, Next.js, Express, Hono, Fastify)
536
+ - All advanced feature examples
537
+ - CLI reference
538
+
539
+ No extra setup needed — just ensure AGENTS.md is in your repo root.
540
+
541
+ ## Add to your project's AGENTS.md
542
+
543
+ \`\`\`markdown
544
+ ## Security — 7h3 Protocol
545
+
546
+ All inter-agent HTTP calls must include a signed 7h3 envelope.
547
+
548
+ Install: npm install @7h3/protocol
549
+ Docs: see https://github.com/IceMasterT/7h3-protocol-aip
550
+
551
+ Verify incoming requests with verifyHttpEnvelope() from @7h3/protocol/http.
552
+ Sign outgoing requests with signEnvelopeEd25519() from @7h3/protocol.
553
+ \`\`\`
554
+ `,
555
+ 'grok': (_sender) => `# 7h3 Protocol — Grok Builder integration
556
+
557
+ Grok Builder reads AGENTS.md. This repo's AGENTS.md includes all integration patterns.
558
+
559
+ ## Add to your project's AGENTS.md
560
+
561
+ \`\`\`markdown
562
+ ## Security — 7h3 Protocol signing
563
+
564
+ All AI agent messages must use 7h3 Protocol (npm: @7h3/protocol).
565
+
566
+ Key rules:
567
+ - Outgoing: createEnvelope() + signEnvelopeEd25519() → x-7h3-envelope header
568
+ - Incoming: verifyHttpEnvelope() from @7h3/protocol/http
569
+ - Wire version is ALWAYS "7h3/0.1" — never change
570
+ - Generate keys: npx 7h3 keygen
571
+ \`\`\`
572
+
573
+ ## MCP config for Grok (if MCP supported)
574
+
575
+ \`\`\`json
576
+ {
577
+ "mcp": {
578
+ "7h3-protocol": {
579
+ "command": "npx",
580
+ "args": ["-y", "@7h3/protocol-mcp"]
581
+ }
582
+ }
583
+ }
584
+ \`\`\`
585
+ `,
586
+ };
587
+ async function cmdAdd(argv) {
588
+ const { values } = parseArgs({
589
+ args: argv,
590
+ options: {
591
+ framework: { type: 'string', short: 'f' },
592
+ sender: { type: 'string', short: 's' },
593
+ output: { type: 'string', short: 'o' },
594
+ },
595
+ strict: false,
596
+ });
597
+ const framework = values.framework ?? '';
598
+ const sender = values.sender ?? 'agent@example.com';
599
+ if (!framework || !ADD_FRAMEWORKS.includes(framework)) {
600
+ process.stdout.write(`Available frameworks:\n`);
601
+ ADD_FRAMEWORKS.forEach(f => process.stdout.write(` ${f}\n`));
602
+ process.stdout.write(`\nUsage: 7h3 add --framework <name> [--sender <id>]\n`);
603
+ return;
604
+ }
605
+ const snippet = FRAMEWORK_SNIPPETS[framework](sender);
606
+ const out = values.output;
607
+ if (out) {
608
+ const { writeFileSync } = await import('node:fs');
609
+ writeFileSync(out, snippet, 'utf8');
610
+ process.stdout.write(`Written to ${out}\n`);
611
+ }
612
+ else {
613
+ process.stdout.write(snippet);
614
+ }
615
+ }
616
+ async function main() {
617
+ const args = process.argv.slice(2);
618
+ const command = args[0] ?? 'help';
619
+ const rest = args.slice(1);
620
+ switch (command) {
621
+ case 'keygen':
622
+ await cmdKeygen(rest);
623
+ break;
624
+ case 'sign':
625
+ await cmdSign(rest);
626
+ break;
627
+ case 'verify':
628
+ await cmdVerify(rest);
629
+ break;
630
+ case 'inspect':
631
+ await cmdInspect(rest);
632
+ break;
633
+ case 'gateway':
634
+ await cmdGateway(rest);
635
+ break;
636
+ case 'keys': {
637
+ const sub = rest[0];
638
+ if (sub === 'serve') {
639
+ await cmdKeysServe(rest.slice(1));
640
+ }
641
+ else {
642
+ die(`Unknown subcommand 'keys ${sub ?? ''}'. Did you mean 'keys serve'?`);
643
+ }
644
+ break;
645
+ }
646
+ case 'add':
647
+ await cmdAdd(rest);
648
+ break;
649
+ case 'help':
650
+ case '--help':
651
+ case '-h':
652
+ process.stdout.write(USAGE);
653
+ break;
654
+ default:
655
+ process.stderr.write(`Unknown command: ${command}\n`);
656
+ process.stdout.write(USAGE);
657
+ process.exit(1);
658
+ }
659
+ }
660
+ main().catch(err => {
661
+ process.stderr.write(`Fatal: ${String(err)}\n`);
662
+ process.exit(1);
663
+ });
package/gateway.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type KeyRegistry } from './keyRegistry';
2
2
  import { type RoutePolicy } from './routePolicy';
3
- import { SlidingWindowRateLimiter } from './rateLimiter';
3
+ import { SlidingWindowRateLimiter, type RateLimitStore } from './rateLimiter';
4
4
  import type { ReplayStore } from './replayStores';
5
5
  export type { KeyRegistry, RoutePolicy };
6
6
  export interface GatewayConfig {
@@ -15,6 +15,14 @@ export interface GatewayConfig {
15
15
  metricsPath?: string;
16
16
  /** Optional distributed replay store — prevents nonce reuse across gateway instances. */
17
17
  replayStore?: ReplayStore;
18
+ /**
19
+ * Optional persistent rate-limit store — required for correct rate limiting
20
+ * whenever the gateway is rebuilt per-request (e.g. inside a Workers/Lambda
21
+ * fetch handler). Without it, rate limiting falls back to the in-memory
22
+ * SlidingWindowRateLimiter, which only works if this Gateway instance
23
+ * persists across the requests it's limiting.
24
+ */
25
+ rateLimitStore?: RateLimitStore;
18
26
  /** Optional capability token registry for capability-based auth. */
19
27
  capabilityRegistry?: {
20
28
  getPublicKey(id: string): Promise<string | null>;
@@ -50,4 +58,12 @@ declare class Protocol7h3Gateway {
50
58
  getRateLimiter(): SlidingWindowRateLimiter;
51
59
  }
52
60
  export declare function createGateway(config: GatewayConfig): Protocol7h3Gateway;
61
+ /**
62
+ * Hardened preset for production deployments: fails fast (rather than
63
+ * silently falling back to permissive defaults) if `defaultPolicy` isn't
64
+ * explicitly `'deny'` or `replayStore` isn't configured. Use this instead of
65
+ * `createGateway()` wherever a misconfiguration should be a deploy-time error,
66
+ * not a runtime security gap discovered later.
67
+ */
68
+ export declare function createProductionGateway(config: GatewayConfig): Protocol7h3Gateway;
53
69
  export { Protocol7h3Gateway };