@nkmc/gateway 0.1.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.
Files changed (130) hide show
  1. package/dist/chunk-56RA53VS.js +37 -0
  2. package/dist/chunk-CZJ75YTV.js +969 -0
  3. package/dist/chunk-QGM4M3NI.js +37 -0
  4. package/dist/http.cjs +1772 -0
  5. package/dist/http.d.cts +49 -0
  6. package/dist/http.d.ts +49 -0
  7. package/dist/http.js +748 -0
  8. package/dist/index.cjs +2436 -0
  9. package/dist/index.d.cts +436 -0
  10. package/dist/index.d.ts +436 -0
  11. package/dist/index.js +1434 -0
  12. package/dist/proxy-ClPcDgsO.d.cts +283 -0
  13. package/dist/proxy-qpda1ANS.d.ts +283 -0
  14. package/dist/proxy.cjs +148 -0
  15. package/dist/proxy.d.cts +6 -0
  16. package/dist/proxy.d.ts +6 -0
  17. package/dist/proxy.js +90 -0
  18. package/dist/testing.cjs +865 -0
  19. package/dist/testing.d.cts +12 -0
  20. package/dist/testing.d.ts +12 -0
  21. package/dist/testing.js +831 -0
  22. package/dist/tunnels-BviBEaih.d.cts +12 -0
  23. package/dist/tunnels-DFHNgmN7.d.ts +12 -0
  24. package/dist/types-C6JC9oTm.d.cts +21 -0
  25. package/dist/types-C6JC9oTm.d.ts +21 -0
  26. package/package.json +47 -0
  27. package/src/__tests__/sqlite-integration.test.ts +384 -0
  28. package/src/credential/d1-vault.ts +134 -0
  29. package/src/credential/memory-vault.ts +50 -0
  30. package/src/credential/types.ts +16 -0
  31. package/src/d1/__tests__/sqlite-adapter.test.ts +75 -0
  32. package/src/d1/sqlite-adapter.ts +59 -0
  33. package/src/d1/types.ts +22 -0
  34. package/src/federation/__tests__/d1-peer-store.test.ts +218 -0
  35. package/src/federation/__tests__/peer-client.test.ts +205 -0
  36. package/src/federation/__tests__/peer-store.test.ts +114 -0
  37. package/src/federation/d1-peer-store.ts +164 -0
  38. package/src/federation/peer-backend.ts +60 -0
  39. package/src/federation/peer-client.ts +122 -0
  40. package/src/federation/peer-store.ts +45 -0
  41. package/src/federation/types.ts +39 -0
  42. package/src/http/app.ts +152 -0
  43. package/src/http/lib/dns.ts +30 -0
  44. package/src/http/middleware/admin-auth.ts +18 -0
  45. package/src/http/middleware/agent-auth.ts +27 -0
  46. package/src/http/middleware/publish-auth.ts +39 -0
  47. package/src/http/routes/__tests__/federation.test.ts +364 -0
  48. package/src/http/routes/__tests__/peers.test.ts +290 -0
  49. package/src/http/routes/__tests__/proxy.test.ts +159 -0
  50. package/src/http/routes/auth.ts +39 -0
  51. package/src/http/routes/byok.ts +62 -0
  52. package/src/http/routes/credentials.ts +40 -0
  53. package/src/http/routes/domains.ts +174 -0
  54. package/src/http/routes/federation.ts +170 -0
  55. package/src/http/routes/fs.ts +89 -0
  56. package/src/http/routes/peers.ts +103 -0
  57. package/src/http/routes/proxy.ts +57 -0
  58. package/src/http/routes/registry.ts +222 -0
  59. package/src/http/routes/tunnels.ts +124 -0
  60. package/src/http.ts +9 -0
  61. package/src/index.ts +63 -0
  62. package/src/metering/d1-store.ts +123 -0
  63. package/src/metering/memory-store.ts +29 -0
  64. package/src/metering/pricing-guard.ts +68 -0
  65. package/src/metering/types.ts +25 -0
  66. package/src/onboard/apis-guru.ts +64 -0
  67. package/src/onboard/index.ts +4 -0
  68. package/src/onboard/manifest.ts +362 -0
  69. package/src/onboard/pipeline.ts +214 -0
  70. package/src/onboard/types.ts +72 -0
  71. package/src/proxy/__tests__/tool-registry.test.ts +93 -0
  72. package/src/proxy/tool-registry.ts +122 -0
  73. package/src/proxy.ts +12 -0
  74. package/src/registry/context7-backend.ts +93 -0
  75. package/src/registry/context7.ts +54 -0
  76. package/src/registry/d1-store.ts +242 -0
  77. package/src/registry/memory-store.ts +101 -0
  78. package/src/registry/openapi-compiler.ts +284 -0
  79. package/src/registry/resolver.ts +196 -0
  80. package/src/registry/rpc-compiler.ts +142 -0
  81. package/src/registry/skill-parser.ts +119 -0
  82. package/src/registry/skill-to-config.ts +239 -0
  83. package/src/registry/source-refresher.ts +83 -0
  84. package/src/registry/types.ts +129 -0
  85. package/src/registry/virtual-files.ts +76 -0
  86. package/src/testing/sqlite-d1.ts +64 -0
  87. package/src/testing.ts +2 -0
  88. package/src/tunnel/__tests__/cloudflare-provider.test.ts +255 -0
  89. package/src/tunnel/__tests__/tunnel.test.ts +542 -0
  90. package/src/tunnel/cloudflare-provider.ts +121 -0
  91. package/src/tunnel/memory-store.ts +30 -0
  92. package/src/tunnel/types.ts +28 -0
  93. package/test/credential/d1-vault.test.ts +127 -0
  94. package/test/credential/injection.test.ts +67 -0
  95. package/test/credential/memory-vault.test.ts +63 -0
  96. package/test/http/app.test.ts +300 -0
  97. package/test/http/byok-e2e.test.ts +240 -0
  98. package/test/http/byok.test.ts +115 -0
  99. package/test/http/credentials.test.ts +57 -0
  100. package/test/http/e2e.test.ts +260 -0
  101. package/test/integration/authenticated-apis.test.ts +185 -0
  102. package/test/integration/free-apis-e2e.test.ts +222 -0
  103. package/test/metering/d1-store.test.ts +82 -0
  104. package/test/metering/memory-store.test.ts +76 -0
  105. package/test/metering/pricing-guard.test.ts +108 -0
  106. package/test/onboard/apis-guru.test.ts +57 -0
  107. package/test/onboard/e2e.test.ts +70 -0
  108. package/test/onboard/pipeline.test.ts +318 -0
  109. package/test/onboard/real-apis.test.ts +483 -0
  110. package/test/registry/compilation-correctness.test.ts +132 -0
  111. package/test/registry/context7-backend.test.ts +88 -0
  112. package/test/registry/context7-e2e.test.ts +92 -0
  113. package/test/registry/context7.test.ts +73 -0
  114. package/test/registry/d1-store.test.ts +184 -0
  115. package/test/registry/integration.test.ts +129 -0
  116. package/test/registry/lazy-mount.test.ts +138 -0
  117. package/test/registry/memory-store.test.ts +171 -0
  118. package/test/registry/openapi-compiler.test.ts +267 -0
  119. package/test/registry/openapi-e2e.test.ts +154 -0
  120. package/test/registry/passthrough-e2e.test.ts +109 -0
  121. package/test/registry/resolver-peer.test.ts +299 -0
  122. package/test/registry/resolver.test.ts +228 -0
  123. package/test/registry/rpc-compiler.test.ts +112 -0
  124. package/test/registry/skill-parser.test.ts +151 -0
  125. package/test/registry/skill-to-config.test.ts +151 -0
  126. package/test/registry/skill-to-rpc-config.test.ts +142 -0
  127. package/test/registry/source-refresher.test.ts +90 -0
  128. package/test/registry/virtual-files.test.ts +96 -0
  129. package/tsconfig.json +4 -0
  130. package/tsup.config.ts +8 -0
package/dist/index.js ADDED
@@ -0,0 +1,1434 @@
1
+ import {
2
+ Context7Backend,
3
+ Context7Client,
4
+ VirtualFileBackend,
5
+ createRegistryResolver,
6
+ credentialRoutes,
7
+ extractDomainPath,
8
+ fetchAndCompile,
9
+ parsePricingAnnotation,
10
+ parseSkillMd,
11
+ queryDnsTxt,
12
+ skillToHttpConfig,
13
+ tunnelRoutes
14
+ } from "./chunk-CZJ75YTV.js";
15
+ import "./chunk-QGM4M3NI.js";
16
+
17
+ // src/registry/memory-store.ts
18
+ var MemoryRegistryStore = class {
19
+ // key = "domain@version"
20
+ records = /* @__PURE__ */ new Map();
21
+ key(domain, version) {
22
+ return `${domain}@${version}`;
23
+ }
24
+ async get(domain) {
25
+ for (const record of this.records.values()) {
26
+ if (record.domain === domain && record.isDefault) return record;
27
+ }
28
+ return null;
29
+ }
30
+ async getVersion(domain, version) {
31
+ return this.records.get(this.key(domain, version)) ?? null;
32
+ }
33
+ async listVersions(domain) {
34
+ const versions = [];
35
+ for (const record of this.records.values()) {
36
+ if (record.domain === domain) {
37
+ versions.push({
38
+ version: record.version,
39
+ status: record.status,
40
+ isDefault: record.isDefault,
41
+ createdAt: record.createdAt,
42
+ updatedAt: record.updatedAt
43
+ });
44
+ }
45
+ }
46
+ return versions.sort((a, b) => b.createdAt - a.createdAt);
47
+ }
48
+ async put(domain, record) {
49
+ this.records.set(this.key(domain, record.version), record);
50
+ }
51
+ async delete(domain) {
52
+ const keysToDelete = [];
53
+ for (const [key, record] of this.records.entries()) {
54
+ if (record.domain === domain) keysToDelete.push(key);
55
+ }
56
+ for (const key of keysToDelete) {
57
+ this.records.delete(key);
58
+ }
59
+ }
60
+ async list() {
61
+ const results = [];
62
+ for (const record of this.records.values()) {
63
+ if (record.isDefault) results.push(toSummary(record));
64
+ }
65
+ return results;
66
+ }
67
+ async search(query) {
68
+ const q = query.toLowerCase();
69
+ const results = [];
70
+ for (const record of this.records.values()) {
71
+ if (!record.isDefault) continue;
72
+ const nameMatch = record.name.toLowerCase().includes(q) || record.description.toLowerCase().includes(q);
73
+ const matched = record.endpoints.filter(
74
+ (e) => e.description.toLowerCase().includes(q) || e.method.toLowerCase().includes(q) || e.path.toLowerCase().includes(q)
75
+ );
76
+ if (nameMatch || matched.length > 0) {
77
+ results.push({
78
+ ...toSummary(record),
79
+ matchedEndpoints: matched.map((e) => ({
80
+ method: e.method,
81
+ path: e.path,
82
+ description: e.description
83
+ }))
84
+ });
85
+ }
86
+ }
87
+ return results;
88
+ }
89
+ };
90
+ function toSummary(record) {
91
+ return {
92
+ domain: record.domain,
93
+ name: record.name,
94
+ description: record.description,
95
+ isFirstParty: record.isFirstParty
96
+ };
97
+ }
98
+
99
+ // src/registry/d1-store.ts
100
+ var CREATE_SERVICES = `
101
+ CREATE TABLE IF NOT EXISTS services (
102
+ domain TEXT NOT NULL,
103
+ version TEXT NOT NULL,
104
+ name TEXT NOT NULL,
105
+ description TEXT,
106
+ roles TEXT,
107
+ skill_md TEXT NOT NULL,
108
+ endpoints TEXT,
109
+ is_first_party INTEGER DEFAULT 0,
110
+ status TEXT DEFAULT 'active',
111
+ is_default INTEGER DEFAULT 1,
112
+ source TEXT,
113
+ sunset_date INTEGER,
114
+ auth_mode TEXT,
115
+ created_at INTEGER NOT NULL,
116
+ updated_at INTEGER NOT NULL,
117
+ PRIMARY KEY (domain, version)
118
+ )`;
119
+ var CREATE_SERVICES_DEFAULT_INDEX = `
120
+ CREATE INDEX IF NOT EXISTS idx_services_default ON services(domain, is_default)`;
121
+ var CREATE_DOMAIN_CHALLENGES = `
122
+ CREATE TABLE IF NOT EXISTS domain_challenges (
123
+ domain TEXT PRIMARY KEY,
124
+ challenge_code TEXT NOT NULL,
125
+ status TEXT NOT NULL DEFAULT 'pending',
126
+ created_at INTEGER NOT NULL,
127
+ verified_at INTEGER,
128
+ expires_at INTEGER NOT NULL
129
+ )`;
130
+ function rowToRecord(row) {
131
+ return {
132
+ domain: row.domain,
133
+ name: row.name,
134
+ description: row.description,
135
+ version: row.version,
136
+ roles: JSON.parse(row.roles),
137
+ skillMd: row.skill_md,
138
+ endpoints: JSON.parse(row.endpoints),
139
+ isFirstParty: row.is_first_party === 1,
140
+ createdAt: row.created_at,
141
+ updatedAt: row.updated_at,
142
+ status: row.status,
143
+ isDefault: row.is_default === 1,
144
+ ...row.source ? { source: JSON.parse(row.source) } : {},
145
+ ...row.sunset_date ? { sunsetDate: row.sunset_date } : {},
146
+ ...row.auth_mode ? { authMode: row.auth_mode } : {}
147
+ };
148
+ }
149
+ function toSummary2(row) {
150
+ return {
151
+ domain: row.domain,
152
+ name: row.name,
153
+ description: row.description,
154
+ isFirstParty: row.is_first_party === 1
155
+ };
156
+ }
157
+ var D1RegistryStore = class _D1RegistryStore {
158
+ constructor(db) {
159
+ this.db = db;
160
+ }
161
+ async initSchema() {
162
+ await this.db.exec(CREATE_SERVICES);
163
+ await this.db.exec(CREATE_SERVICES_DEFAULT_INDEX);
164
+ await this.db.exec(CREATE_DOMAIN_CHALLENGES);
165
+ }
166
+ async get(domain) {
167
+ const row = await this.db.prepare("SELECT * FROM services WHERE domain = ? AND is_default = 1").bind(domain).first();
168
+ return row ? rowToRecord(row) : null;
169
+ }
170
+ async getVersion(domain, version) {
171
+ const row = await this.db.prepare("SELECT * FROM services WHERE domain = ? AND version = ?").bind(domain, version).first();
172
+ return row ? rowToRecord(row) : null;
173
+ }
174
+ async listVersions(domain) {
175
+ const { results } = await this.db.prepare(
176
+ "SELECT version, status, is_default, created_at, updated_at FROM services WHERE domain = ? ORDER BY created_at DESC"
177
+ ).bind(domain).all();
178
+ return results.map((row) => ({
179
+ version: row.version,
180
+ status: row.status,
181
+ isDefault: row.is_default === 1,
182
+ createdAt: row.created_at,
183
+ updatedAt: row.updated_at
184
+ }));
185
+ }
186
+ /** Max endpoints JSON size before stripping verbose fields (parameters, requestBody, responses). */
187
+ static ENDPOINTS_SIZE_LIMIT = 8e5;
188
+ // ~800 KB, well under D1's ~1 MB per-value limit
189
+ async put(domain, record) {
190
+ let endpointsJson = JSON.stringify(record.endpoints);
191
+ if (endpointsJson.length > _D1RegistryStore.ENDPOINTS_SIZE_LIMIT) {
192
+ const slim = record.endpoints.map(({ method, path, description, price, pricing }) => ({
193
+ method,
194
+ path,
195
+ description,
196
+ ...price ? { price } : {},
197
+ ...pricing ? { pricing } : {}
198
+ }));
199
+ endpointsJson = JSON.stringify(slim);
200
+ }
201
+ await this.db.prepare(
202
+ `INSERT OR REPLACE INTO services
203
+ (domain, version, name, description, roles, skill_md, endpoints, is_first_party, status, is_default, source, sunset_date, auth_mode, created_at, updated_at)
204
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
205
+ ).bind(
206
+ domain,
207
+ record.version,
208
+ record.name,
209
+ record.description,
210
+ JSON.stringify(record.roles),
211
+ record.skillMd,
212
+ endpointsJson,
213
+ record.isFirstParty ? 1 : 0,
214
+ record.status,
215
+ record.isDefault ? 1 : 0,
216
+ record.source ? JSON.stringify(record.source) : null,
217
+ record.sunsetDate ?? null,
218
+ record.authMode ?? null,
219
+ record.createdAt,
220
+ record.updatedAt
221
+ ).run();
222
+ }
223
+ async delete(domain) {
224
+ await this.db.prepare("DELETE FROM services WHERE domain = ?").bind(domain).run();
225
+ }
226
+ async list() {
227
+ const { results } = await this.db.prepare("SELECT domain, name, description, is_first_party FROM services WHERE is_default = 1").all();
228
+ return results.map(toSummary2);
229
+ }
230
+ async search(query) {
231
+ const pattern = `%${query}%`;
232
+ const { results: rows } = await this.db.prepare(
233
+ `SELECT * FROM services
234
+ WHERE is_default = 1 AND (name LIKE ? OR description LIKE ? OR endpoints LIKE ?)`
235
+ ).bind(pattern, pattern, pattern).all();
236
+ const q = query.toLowerCase();
237
+ return rows.map((row) => {
238
+ const endpoints = JSON.parse(row.endpoints);
239
+ const matched = endpoints.filter(
240
+ (e) => e.description.toLowerCase().includes(q) || e.method.toLowerCase().includes(q) || e.path.toLowerCase().includes(q)
241
+ );
242
+ return {
243
+ ...toSummary2(row),
244
+ matchedEndpoints: matched.map((e) => ({
245
+ method: e.method,
246
+ path: e.path,
247
+ description: e.description
248
+ }))
249
+ };
250
+ });
251
+ }
252
+ async stats() {
253
+ const row = await this.db.prepare(
254
+ `SELECT COUNT(*) as service_count, COALESCE(SUM(json_array_length(endpoints)), 0) as endpoint_count
255
+ FROM services WHERE is_default = 1`
256
+ ).first();
257
+ return {
258
+ serviceCount: row?.service_count ?? 0,
259
+ endpointCount: row?.endpoint_count ?? 0
260
+ };
261
+ }
262
+ };
263
+
264
+ // src/metering/memory-store.ts
265
+ var MemoryMeterStore = class {
266
+ records = [];
267
+ async record(entry) {
268
+ this.records.push(entry);
269
+ }
270
+ async query(filter) {
271
+ return this.records.filter((r) => this.matches(r, filter));
272
+ }
273
+ async sum(filter) {
274
+ const matched = this.records.filter((r) => this.matches(r, filter));
275
+ const total = matched.reduce((acc, r) => acc + r.cost, 0);
276
+ const currency = matched[0]?.currency ?? "USDC";
277
+ return { total, currency };
278
+ }
279
+ matches(record, filter) {
280
+ if (filter.domain && record.domain !== filter.domain) return false;
281
+ if (filter.agentId && record.agentId !== filter.agentId) return false;
282
+ if (filter.developerId && record.developerId !== filter.developerId) return false;
283
+ if (filter.from && record.timestamp < filter.from) return false;
284
+ if (filter.to && record.timestamp > filter.to) return false;
285
+ return true;
286
+ }
287
+ };
288
+
289
+ // src/metering/d1-store.ts
290
+ var CREATE_METER_RECORDS = `
291
+ CREATE TABLE IF NOT EXISTS meter_records (
292
+ id TEXT PRIMARY KEY,
293
+ timestamp INTEGER NOT NULL,
294
+ domain TEXT NOT NULL,
295
+ version TEXT NOT NULL,
296
+ endpoint TEXT NOT NULL,
297
+ agent_id TEXT NOT NULL,
298
+ developer_id TEXT,
299
+ cost REAL NOT NULL,
300
+ currency TEXT NOT NULL DEFAULT 'USDC'
301
+ )`;
302
+ var CREATE_METER_INDEX_DOMAIN = `
303
+ CREATE INDEX IF NOT EXISTS idx_meter_domain ON meter_records(domain, timestamp)`;
304
+ var CREATE_METER_INDEX_AGENT = `
305
+ CREATE INDEX IF NOT EXISTS idx_meter_agent ON meter_records(agent_id, timestamp)`;
306
+ var D1MeterStore = class {
307
+ constructor(db) {
308
+ this.db = db;
309
+ }
310
+ async initSchema() {
311
+ await this.db.exec(CREATE_METER_RECORDS);
312
+ await this.db.exec(CREATE_METER_INDEX_DOMAIN);
313
+ await this.db.exec(CREATE_METER_INDEX_AGENT);
314
+ }
315
+ async record(entry) {
316
+ await this.db.prepare(
317
+ `INSERT INTO meter_records (id, timestamp, domain, version, endpoint, agent_id, developer_id, cost, currency)
318
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
319
+ ).bind(
320
+ entry.id,
321
+ entry.timestamp,
322
+ entry.domain,
323
+ entry.version,
324
+ entry.endpoint,
325
+ entry.agentId,
326
+ entry.developerId ?? null,
327
+ entry.cost,
328
+ entry.currency
329
+ ).run();
330
+ }
331
+ async query(filter) {
332
+ const { sql, bindings } = this.buildQuery("SELECT *", filter);
333
+ const { results } = await this.db.prepare(sql).bind(...bindings).all();
334
+ return results.map(rowToRecord2);
335
+ }
336
+ async sum(filter) {
337
+ const { sql, bindings } = this.buildQuery("SELECT COALESCE(SUM(cost), 0) as total, COALESCE(MIN(currency), 'USDC') as currency", filter);
338
+ const row = await this.db.prepare(sql).bind(...bindings).first();
339
+ return { total: row?.total ?? 0, currency: row?.currency ?? "USDC" };
340
+ }
341
+ buildQuery(select, filter) {
342
+ const conditions = [];
343
+ const bindings = [];
344
+ if (filter.domain) {
345
+ conditions.push("domain = ?");
346
+ bindings.push(filter.domain);
347
+ }
348
+ if (filter.agentId) {
349
+ conditions.push("agent_id = ?");
350
+ bindings.push(filter.agentId);
351
+ }
352
+ if (filter.developerId) {
353
+ conditions.push("developer_id = ?");
354
+ bindings.push(filter.developerId);
355
+ }
356
+ if (filter.from) {
357
+ conditions.push("timestamp >= ?");
358
+ bindings.push(filter.from);
359
+ }
360
+ if (filter.to) {
361
+ conditions.push("timestamp <= ?");
362
+ bindings.push(filter.to);
363
+ }
364
+ let sql = `${select} FROM meter_records`;
365
+ if (conditions.length > 0) {
366
+ sql += ` WHERE ${conditions.join(" AND ")}`;
367
+ }
368
+ sql += " ORDER BY timestamp DESC";
369
+ return { sql, bindings };
370
+ }
371
+ };
372
+ function rowToRecord2(row) {
373
+ return {
374
+ id: row.id,
375
+ timestamp: row.timestamp,
376
+ domain: row.domain,
377
+ version: row.version,
378
+ endpoint: row.endpoint,
379
+ agentId: row.agent_id,
380
+ ...row.developer_id ? { developerId: row.developer_id } : {},
381
+ cost: row.cost,
382
+ currency: row.currency
383
+ };
384
+ }
385
+
386
+ // src/metering/pricing-guard.ts
387
+ function lookupPricing(record, method, path) {
388
+ for (const ep of record.endpoints) {
389
+ if (ep.method.toUpperCase() !== method.toUpperCase()) continue;
390
+ if (matchPath(ep.path, path)) {
391
+ return ep.pricing ?? null;
392
+ }
393
+ }
394
+ return null;
395
+ }
396
+ function checkAccess(record) {
397
+ if (record.status === "sunset") {
398
+ return { allowed: false, reason: "Service has been sunset" };
399
+ }
400
+ if (record.sunsetDate && record.sunsetDate < Date.now()) {
401
+ return { allowed: false, reason: "Service sunset date has passed" };
402
+ }
403
+ return { allowed: true };
404
+ }
405
+ async function meter(store, opts) {
406
+ const entry = {
407
+ id: `m_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
408
+ timestamp: Date.now(),
409
+ domain: opts.domain,
410
+ version: opts.version,
411
+ endpoint: opts.endpoint,
412
+ agentId: opts.agentId,
413
+ developerId: opts.developerId,
414
+ cost: opts.pricing.cost,
415
+ currency: opts.pricing.currency
416
+ };
417
+ await store.record(entry);
418
+ return entry;
419
+ }
420
+ function matchPath(pattern, actual) {
421
+ const patternParts = pattern.split("/").filter(Boolean);
422
+ const actualParts = actual.split("/").filter(Boolean);
423
+ if (patternParts.length !== actualParts.length) return false;
424
+ for (let i = 0; i < patternParts.length; i++) {
425
+ if (patternParts[i].startsWith(":")) continue;
426
+ if (patternParts[i] !== actualParts[i]) return false;
427
+ }
428
+ return true;
429
+ }
430
+
431
+ // src/credential/memory-vault.ts
432
+ var MemoryCredentialVault = class {
433
+ // key = "pool:domain" or "byok:domain:developerId"
434
+ credentials = /* @__PURE__ */ new Map();
435
+ poolKey(domain) {
436
+ return `pool:${domain}`;
437
+ }
438
+ byokKey(domain, developerId) {
439
+ return `byok:${domain}:${developerId}`;
440
+ }
441
+ async get(domain, developerId) {
442
+ if (developerId) {
443
+ const byok = this.credentials.get(this.byokKey(domain, developerId));
444
+ if (byok) return byok;
445
+ }
446
+ return this.credentials.get(this.poolKey(domain)) ?? null;
447
+ }
448
+ async putPool(domain, auth) {
449
+ this.credentials.set(this.poolKey(domain), { domain, auth, scope: "pool" });
450
+ }
451
+ async putByok(domain, developerId, auth) {
452
+ this.credentials.set(this.byokKey(domain, developerId), {
453
+ domain,
454
+ auth,
455
+ scope: "byok",
456
+ developerId
457
+ });
458
+ }
459
+ async delete(domain, developerId) {
460
+ if (developerId) {
461
+ this.credentials.delete(this.byokKey(domain, developerId));
462
+ } else {
463
+ this.credentials.delete(this.poolKey(domain));
464
+ }
465
+ }
466
+ async listDomains() {
467
+ const domains = /* @__PURE__ */ new Set();
468
+ for (const cred of this.credentials.values()) {
469
+ domains.add(cred.domain);
470
+ }
471
+ return Array.from(domains);
472
+ }
473
+ };
474
+
475
+ // src/credential/d1-vault.ts
476
+ var CREATE_CREDENTIALS = `
477
+ CREATE TABLE IF NOT EXISTS credentials (
478
+ domain TEXT NOT NULL,
479
+ scope TEXT NOT NULL DEFAULT 'pool',
480
+ developer_id TEXT NOT NULL DEFAULT '',
481
+ auth_encrypted TEXT NOT NULL,
482
+ created_at INTEGER NOT NULL,
483
+ updated_at INTEGER NOT NULL,
484
+ PRIMARY KEY (domain, scope, developer_id)
485
+ )`;
486
+ async function encrypt(auth, key) {
487
+ const iv = crypto.getRandomValues(new Uint8Array(12));
488
+ const plaintext = new TextEncoder().encode(JSON.stringify(auth));
489
+ const ciphertext = await crypto.subtle.encrypt(
490
+ { name: "AES-GCM", iv },
491
+ key,
492
+ plaintext
493
+ );
494
+ const combined = new Uint8Array(iv.length + ciphertext.byteLength);
495
+ combined.set(iv);
496
+ combined.set(new Uint8Array(ciphertext), iv.length);
497
+ return btoa(String.fromCharCode(...combined));
498
+ }
499
+ async function decrypt(encoded, key) {
500
+ const bytes = Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
501
+ const iv = bytes.slice(0, 12);
502
+ const ciphertext = bytes.slice(12);
503
+ try {
504
+ const plaintext = await crypto.subtle.decrypt(
505
+ { name: "AES-GCM", iv },
506
+ key,
507
+ ciphertext
508
+ );
509
+ return JSON.parse(new TextDecoder().decode(plaintext));
510
+ } catch {
511
+ return JSON.parse(atob(encoded));
512
+ }
513
+ }
514
+ var D1CredentialVault = class {
515
+ constructor(db, encryptionKey) {
516
+ this.db = db;
517
+ this.encryptionKey = encryptionKey;
518
+ }
519
+ async initSchema() {
520
+ await this.db.exec(CREATE_CREDENTIALS);
521
+ }
522
+ async get(domain, developerId) {
523
+ if (developerId) {
524
+ const byok = await this.db.prepare("SELECT * FROM credentials WHERE domain = ? AND scope = 'byok' AND developer_id = ?").bind(domain, developerId).first();
525
+ if (byok) return await this.rowToCredential(byok);
526
+ }
527
+ const pool = await this.db.prepare("SELECT * FROM credentials WHERE domain = ? AND scope = 'pool' AND developer_id = ''").bind(domain).first();
528
+ return pool ? await this.rowToCredential(pool) : null;
529
+ }
530
+ async putPool(domain, auth) {
531
+ const now = Date.now();
532
+ await this.db.prepare(
533
+ `INSERT OR REPLACE INTO credentials (domain, scope, developer_id, auth_encrypted, created_at, updated_at)
534
+ VALUES (?, 'pool', '', ?, ?, ?)`
535
+ ).bind(domain, await encrypt(auth, this.encryptionKey), now, now).run();
536
+ }
537
+ async putByok(domain, developerId, auth) {
538
+ const now = Date.now();
539
+ await this.db.prepare(
540
+ `INSERT OR REPLACE INTO credentials (domain, scope, developer_id, auth_encrypted, created_at, updated_at)
541
+ VALUES (?, 'byok', ?, ?, ?, ?)`
542
+ ).bind(domain, developerId, await encrypt(auth, this.encryptionKey), now, now).run();
543
+ }
544
+ async delete(domain, developerId) {
545
+ if (developerId) {
546
+ await this.db.prepare("DELETE FROM credentials WHERE domain = ? AND scope = 'byok' AND developer_id = ?").bind(domain, developerId).run();
547
+ } else {
548
+ await this.db.prepare("DELETE FROM credentials WHERE domain = ? AND scope = 'pool' AND developer_id = ''").bind(domain).run();
549
+ }
550
+ }
551
+ async listDomains() {
552
+ const { results } = await this.db.prepare("SELECT DISTINCT domain FROM credentials").all();
553
+ return results.map((r) => r.domain);
554
+ }
555
+ async rowToCredential(row) {
556
+ return {
557
+ domain: row.domain,
558
+ auth: await decrypt(row.auth_encrypted, this.encryptionKey),
559
+ scope: row.scope,
560
+ ...row.developer_id ? { developerId: row.developer_id } : {}
561
+ };
562
+ }
563
+ };
564
+
565
+ // src/onboard/pipeline.ts
566
+ import { AgentFs } from "@nkmc/agent-fs";
567
+
568
+ // src/registry/rpc-compiler.ts
569
+ function compileRpcDef(domain, rpcDef, options) {
570
+ const convention = rpcDef.convention ?? "raw";
571
+ const endpoints = rpcDef.methods.map((m) => ({
572
+ method: "RPC",
573
+ path: m.rpcMethod,
574
+ description: m.description
575
+ }));
576
+ const resourceMap = /* @__PURE__ */ new Map();
577
+ for (const m of rpcDef.methods) {
578
+ const resName = m.resource ?? inferResource(m.rpcMethod);
579
+ if (!resourceMap.has(resName)) {
580
+ resourceMap.set(resName, { name: resName, methods: {} });
581
+ }
582
+ const entry = resourceMap.get(resName);
583
+ if (m.fsOp) {
584
+ entry.methods[m.fsOp] = m.rpcMethod;
585
+ }
586
+ }
587
+ const rpcMeta = {
588
+ rpcUrl: rpcDef.url,
589
+ convention,
590
+ resources: Array.from(resourceMap.values())
591
+ };
592
+ const skillMd = generateSkillMd(domain, rpcDef, endpoints);
593
+ const now = Date.now();
594
+ const record = {
595
+ domain,
596
+ name: domain,
597
+ description: `JSON-RPC service at ${domain}`,
598
+ version: options?.version ?? "1.0",
599
+ roles: ["agent"],
600
+ skillMd,
601
+ endpoints,
602
+ isFirstParty: options?.isFirstParty ?? false,
603
+ createdAt: now,
604
+ updatedAt: now,
605
+ status: "active",
606
+ isDefault: true,
607
+ source: {
608
+ type: "jsonrpc",
609
+ url: rpcDef.url,
610
+ rpc: rpcMeta
611
+ }
612
+ };
613
+ return { record, skillMd };
614
+ }
615
+ function inferResource(rpcMethod) {
616
+ const underscoreIdx = rpcMethod.indexOf("_");
617
+ if (underscoreIdx < 0) return rpcMethod;
618
+ const action = rpcMethod.slice(underscoreIdx + 1);
619
+ const verbPrefixes = ["get", "send", "subscribe", "unsubscribe", "new", "call"];
620
+ let noun = action;
621
+ for (const prefix of verbPrefixes) {
622
+ if (action.startsWith(prefix) && action.length > prefix.length) {
623
+ noun = action.slice(prefix.length);
624
+ break;
625
+ }
626
+ }
627
+ const camelMatch = noun.match(/^([A-Z][a-z]+)/);
628
+ if (camelMatch) {
629
+ noun = camelMatch[1];
630
+ }
631
+ const lower = noun.toLowerCase();
632
+ return lower.endsWith("s") ? lower : lower + "s";
633
+ }
634
+ function generateSkillMd(domain, rpcDef, endpoints) {
635
+ const lines = [
636
+ "---",
637
+ `name: "${domain}"`,
638
+ `version: "1.0"`,
639
+ `roles: [agent]`,
640
+ "---",
641
+ "",
642
+ `# ${domain}`,
643
+ "",
644
+ `JSON-RPC service at ${rpcDef.url}`,
645
+ "",
646
+ `Convention: ${rpcDef.convention ?? "raw"}`,
647
+ "",
648
+ "## RPC Methods",
649
+ "",
650
+ "| method | description |",
651
+ "|--------|-------------|"
652
+ ];
653
+ for (const ep of endpoints) {
654
+ lines.push(`| ${ep.path} | ${ep.description} |`);
655
+ }
656
+ return lines.join("\n");
657
+ }
658
+
659
+ // src/onboard/pipeline.ts
660
+ var OnboardPipeline = class {
661
+ store;
662
+ vault;
663
+ smokeTest;
664
+ concurrency;
665
+ fetchFn;
666
+ onProgress;
667
+ constructor(options) {
668
+ this.store = options.store;
669
+ this.vault = options.vault;
670
+ this.smokeTest = options.smokeTest !== false;
671
+ this.concurrency = options.concurrency ?? 5;
672
+ this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
673
+ this.onProgress = options.onProgress;
674
+ }
675
+ /** Onboard a single service */
676
+ async onboardOne(entry) {
677
+ const start = Date.now();
678
+ const base = {
679
+ domain: entry.domain,
680
+ source: "none",
681
+ endpoints: 0,
682
+ resources: 0,
683
+ hasCredentials: false
684
+ };
685
+ if (entry.disabled) {
686
+ return { ...base, status: "skipped", durationMs: Date.now() - start };
687
+ }
688
+ try {
689
+ if (entry.specUrl) {
690
+ const result = await fetchAndCompile(entry.specUrl, { domain: entry.domain }, this.fetchFn);
691
+ await this.store.put(entry.domain, result.record);
692
+ base.source = "openapi";
693
+ base.endpoints = result.record.endpoints.length;
694
+ base.resources = result.resources.length;
695
+ } else if (entry.skillMdUrl) {
696
+ const resp = await this.fetchFn(entry.skillMdUrl);
697
+ if (!resp.ok) throw new Error(`Failed to fetch skill.md: ${resp.status}`);
698
+ const md = await resp.text();
699
+ const record = parseSkillMd(entry.domain, md);
700
+ await this.store.put(entry.domain, record);
701
+ base.source = "wellknown";
702
+ base.endpoints = record.endpoints.length;
703
+ } else if (entry.skillMd) {
704
+ const record = parseSkillMd(entry.domain, entry.skillMd);
705
+ await this.store.put(entry.domain, record);
706
+ base.source = "skillmd";
707
+ base.endpoints = record.endpoints.length;
708
+ } else if (entry.rpcDef) {
709
+ const { record } = compileRpcDef(entry.domain, entry.rpcDef);
710
+ await this.store.put(entry.domain, record);
711
+ base.source = "jsonrpc";
712
+ base.endpoints = record.endpoints.length;
713
+ base.resources = record.source?.rpc?.resources.length ?? 0;
714
+ } else {
715
+ return { ...base, status: "skipped", error: "No spec, skillMdUrl, or skillMd provided", durationMs: Date.now() - start };
716
+ }
717
+ if (entry.auth && this.vault) {
718
+ const auth = resolveAuth(entry.auth);
719
+ await this.vault.putPool(entry.domain, auth);
720
+ base.hasCredentials = true;
721
+ }
722
+ if (this.smokeTest) {
723
+ base.smokeTest = await this.runSmokeTest(entry.domain, base.hasCredentials);
724
+ }
725
+ return { ...base, status: "ok", durationMs: Date.now() - start };
726
+ } catch (err) {
727
+ return {
728
+ ...base,
729
+ status: "failed",
730
+ error: err instanceof Error ? err.message : String(err),
731
+ durationMs: Date.now() - start
732
+ };
733
+ }
734
+ }
735
+ /** Onboard many services with controlled concurrency */
736
+ async onboardMany(entries) {
737
+ const start = Date.now();
738
+ const results = [];
739
+ let index = 0;
740
+ for (let i = 0; i < entries.length; i += this.concurrency) {
741
+ const batch = entries.slice(i, i + this.concurrency);
742
+ const batchResults = await Promise.all(
743
+ batch.map(async (entry) => {
744
+ const result = await this.onboardOne(entry);
745
+ const idx = index++;
746
+ this.onProgress?.(result, idx, entries.length);
747
+ return result;
748
+ })
749
+ );
750
+ results.push(...batchResults);
751
+ }
752
+ return {
753
+ total: results.length,
754
+ ok: results.filter((r) => r.status === "ok").length,
755
+ failed: results.filter((r) => r.status === "failed").length,
756
+ skipped: results.filter((r) => r.status === "skipped").length,
757
+ results,
758
+ durationMs: Date.now() - start
759
+ };
760
+ }
761
+ async runSmokeTest(domain, hasCredentials) {
762
+ const resolverOpts = this.vault ? { store: this.store, vault: this.vault, wrapVirtualFiles: false } : { store: this.store, wrapVirtualFiles: false };
763
+ const { onMiss, listDomains } = createRegistryResolver(resolverOpts);
764
+ const fs = new AgentFs({ mounts: [], onMiss, listDomains });
765
+ const test = { ls: false, cat: false };
766
+ const lsResult = await fs.execute(`ls /${domain}/`);
767
+ test.ls = lsResult.ok === true;
768
+ if (test.ls && lsResult.ok) {
769
+ const entries = lsResult.data;
770
+ const resource = entries.find((e) => e.endsWith("/") && !e.startsWith("_"));
771
+ if (resource) {
772
+ const catResult = await fs.execute(`ls /${domain}/${resource}`);
773
+ test.cat = catResult.ok === true;
774
+ test.catEndpoint = `ls /${domain}/${resource}`;
775
+ } else if (entries.includes("_api/")) {
776
+ const apiResult = await fs.execute(`ls /${domain}/_api/`);
777
+ test.cat = apiResult.ok === true;
778
+ test.catEndpoint = `ls /${domain}/_api/`;
779
+ }
780
+ }
781
+ return test;
782
+ }
783
+ };
784
+ function resolveAuth(auth) {
785
+ const resolve = (val) => {
786
+ if (!val) return void 0;
787
+ const match = val.match(/^\$\{(\w+)\}$/);
788
+ if (match) {
789
+ const envVal = process.env[match[1]];
790
+ if (!envVal) throw new Error(`Environment variable ${match[1]} is not set`);
791
+ return envVal;
792
+ }
793
+ return val;
794
+ };
795
+ if (auth.type === "bearer") {
796
+ const token = resolve(auth.token);
797
+ if (!token) throw new Error("Bearer auth requires token");
798
+ return { type: "bearer", token, ...auth.prefix ? { prefix: auth.prefix } : {} };
799
+ }
800
+ if (auth.type === "api-key") {
801
+ const header = resolve(auth.header);
802
+ const key = resolve(auth.key);
803
+ if (!header || !key) throw new Error("API key auth requires header and key");
804
+ return { type: "api-key", header, key };
805
+ }
806
+ if (auth.type === "basic") {
807
+ const username = resolve(auth.username);
808
+ const password = resolve(auth.password);
809
+ if (!username || !password) throw new Error("Basic auth requires username and password");
810
+ return { type: "basic", username, password };
811
+ }
812
+ if (auth.type === "oauth2") {
813
+ const tokenUrl = resolve(auth.tokenUrl);
814
+ const clientId = resolve(auth.clientId);
815
+ const clientSecret = resolve(auth.clientSecret);
816
+ if (!tokenUrl || !clientId || !clientSecret) throw new Error("OAuth2 auth requires tokenUrl, clientId, and clientSecret");
817
+ return { type: "oauth2", tokenUrl, clientId, clientSecret, ...auth.scope ? { scope: auth.scope } : {} };
818
+ }
819
+ throw new Error(`Unknown auth type: ${auth.type}`);
820
+ }
821
+
822
+ // src/onboard/apis-guru.ts
823
+ var APIS_GURU_LIST = "https://api.apis.guru/v2/list.json";
824
+ async function discoverFromApisGuru(options) {
825
+ const fetchFn = options?.fetchFn ?? globalThis.fetch.bind(globalThis);
826
+ const limit = options?.limit ?? 100;
827
+ const filter = options?.filter?.toLowerCase();
828
+ const resp = await fetchFn(APIS_GURU_LIST);
829
+ if (!resp.ok) throw new Error(`apis.guru fetch failed: ${resp.status}`);
830
+ const catalog = await resp.json();
831
+ const entries = [];
832
+ for (const [key, api] of Object.entries(catalog)) {
833
+ if (entries.length >= limit) break;
834
+ const version = api.versions[api.preferred];
835
+ if (!version?.swaggerUrl) continue;
836
+ const title = version.info?.title ?? key;
837
+ const desc = version.info?.description ?? "";
838
+ if (filter) {
839
+ const text = `${key} ${title} ${desc}`.toLowerCase();
840
+ if (!text.includes(filter)) continue;
841
+ }
842
+ const domain = key.split(":")[0];
843
+ entries.push({
844
+ domain,
845
+ specUrl: version.swaggerUrl,
846
+ tags: ["apis-guru", "public"]
847
+ });
848
+ }
849
+ return entries;
850
+ }
851
+
852
+ // src/onboard/manifest.ts
853
+ var FREE_APIS = [
854
+ {
855
+ domain: "petstore3.swagger.io",
856
+ specUrl: "https://petstore3.swagger.io/api/v3/openapi.json",
857
+ tags: ["demo", "free"]
858
+ },
859
+ {
860
+ domain: "api.weather.gov",
861
+ specUrl: "https://api.weather.gov/openapi.json",
862
+ tags: ["weather", "government", "free"]
863
+ },
864
+ {
865
+ domain: "en.wikipedia.org",
866
+ specUrl: "https://en.wikipedia.org/api/rest_v1/?spec",
867
+ tags: ["knowledge", "encyclopedia", "free"]
868
+ }
869
+ ];
870
+ var FREEMIUM_APIS = [
871
+ {
872
+ domain: "api.github.com",
873
+ specUrl: "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2022-11-28.json",
874
+ auth: { type: "bearer", token: "${GITHUB_TOKEN}" },
875
+ tags: ["developer-tools", "vcs", "freemium"]
876
+ },
877
+ {
878
+ domain: "huggingface.co",
879
+ specUrl: "https://huggingface.co/.well-known/openapi.json",
880
+ auth: { type: "bearer", token: "${HF_TOKEN}" },
881
+ tags: ["ai", "ml", "models", "freemium"]
882
+ }
883
+ ];
884
+ var DEVELOPER_TOOL_APIS = [
885
+ {
886
+ domain: "gitlab.com",
887
+ specUrl: "https://gitlab.com/gitlab-org/gitlab/-/raw/master/doc/api/openapi/openapi.yaml",
888
+ auth: { type: "bearer", token: "${GITLAB_TOKEN}" },
889
+ tags: ["developer-tools", "vcs"]
890
+ },
891
+ {
892
+ domain: "api.vercel.com",
893
+ specUrl: "https://openapi.vercel.sh",
894
+ auth: { type: "bearer", token: "${VERCEL_TOKEN}" },
895
+ tags: ["developer-tools", "hosting"]
896
+ },
897
+ {
898
+ domain: "sentry.io",
899
+ specUrl: "https://raw.githubusercontent.com/getsentry/sentry-api-schema/main/openapi-derefed.json",
900
+ auth: { type: "bearer", token: "${SENTRY_AUTH_TOKEN}" },
901
+ tags: ["developer-tools", "monitoring"]
902
+ },
903
+ {
904
+ domain: "api.pagerduty.com",
905
+ specUrl: "https://raw.githubusercontent.com/PagerDuty/api-schema/main/reference/REST/openapiv3.json",
906
+ auth: { type: "bearer", token: "${PAGERDUTY_TOKEN}" },
907
+ tags: ["developer-tools", "incident-management"]
908
+ }
909
+ ];
910
+ var AI_APIS = [
911
+ {
912
+ domain: "api.mistral.ai",
913
+ specUrl: "https://raw.githubusercontent.com/mistralai/platform-docs-public/main/openapi.yaml",
914
+ auth: { type: "bearer", token: "${MISTRAL_API_KEY}" },
915
+ tags: ["ai", "llm"],
916
+ disabled: true
917
+ // upstream YAML spec has unescaped quotes in example data
918
+ },
919
+ {
920
+ domain: "api.openai.com",
921
+ specUrl: "https://raw.githubusercontent.com/openai/openai-openapi/manual_spec/openapi.yaml",
922
+ auth: { type: "bearer", token: "${OPENAI_API_KEY}" },
923
+ tags: ["ai", "llm"]
924
+ },
925
+ {
926
+ domain: "openrouter.ai",
927
+ specUrl: "https://openrouter.ai/openapi.json",
928
+ auth: { type: "bearer", token: "${OPENROUTER_API_KEY}" },
929
+ tags: ["ai", "llm", "gateway"]
930
+ }
931
+ ];
932
+ var CLOUD_APIS = [
933
+ {
934
+ domain: "api.cloudflare.com",
935
+ specUrl: "https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.yaml",
936
+ auth: { type: "bearer", token: "${CLOUDFLARE_API_TOKEN}" },
937
+ tags: ["cloud", "cdn", "dns"]
938
+ },
939
+ {
940
+ domain: "api.digitalocean.com",
941
+ specUrl: "https://raw.githubusercontent.com/digitalocean/openapi/main/specification/DigitalOcean-public.v2.yaml",
942
+ auth: { type: "bearer", token: "${DIGITALOCEAN_TOKEN}" },
943
+ tags: ["cloud", "infrastructure"]
944
+ },
945
+ {
946
+ domain: "fly.io",
947
+ specUrl: "https://docs.machines.dev/spec/openapi3.json",
948
+ auth: { type: "bearer", token: "${FLY_API_TOKEN}" },
949
+ tags: ["cloud", "deployment"]
950
+ },
951
+ {
952
+ domain: "api.render.com",
953
+ specUrl: "https://api-docs.render.com/v1.0/openapi/render-public-api-1.json",
954
+ auth: { type: "bearer", token: "${RENDER_API_KEY}" },
955
+ tags: ["cloud", "deployment"]
956
+ }
957
+ ];
958
+ var PRODUCTIVITY_APIS = [
959
+ {
960
+ domain: "api.notion.com",
961
+ specUrl: "https://raw.githubusercontent.com/makenotion/notion-mcp-server/main/scripts/notion-openapi.json",
962
+ auth: { type: "bearer", token: "${NOTION_API_KEY}" },
963
+ tags: ["productivity", "database"]
964
+ },
965
+ {
966
+ domain: "app.asana.com",
967
+ specUrl: "https://raw.githubusercontent.com/Asana/openapi/master/defs/asana_oas.yaml",
968
+ auth: { type: "bearer", token: "${ASANA_ACCESS_TOKEN}" },
969
+ tags: ["productivity", "project-management"]
970
+ },
971
+ {
972
+ domain: "jira.atlassian.com",
973
+ specUrl: "https://developer.atlassian.com/cloud/jira/platform/swagger-v3.v3.json",
974
+ auth: { type: "bearer", token: "${ATLASSIAN_API_TOKEN}" },
975
+ tags: ["productivity", "project-management"]
976
+ },
977
+ {
978
+ domain: "api.spotify.com",
979
+ specUrl: "https://raw.githubusercontent.com/sonallux/spotify-web-api/main/fixed-spotify-open-api.yml",
980
+ auth: { type: "bearer", token: "${SPOTIFY_ACCESS_TOKEN}" },
981
+ tags: ["media", "music"]
982
+ },
983
+ {
984
+ domain: "api.getpostman.com",
985
+ specUrl: "https://api.apis.guru/v2/specs/getpostman.com/1.20.0/openapi.json",
986
+ auth: { type: "api-key", header: "X-Api-Key", key: "${POSTMAN_API_KEY}" },
987
+ tags: ["developer-tools", "api-testing"]
988
+ }
989
+ ];
990
+ var DEVOPS_APIS = [
991
+ {
992
+ domain: "circleci.com",
993
+ specUrl: "https://circleci.com/api/v2/openapi.json",
994
+ auth: { type: "bearer", token: "${CIRCLECI_TOKEN}" },
995
+ tags: ["devops", "ci-cd"]
996
+ },
997
+ {
998
+ domain: "api.datadoghq.com",
999
+ specUrl: "https://raw.githubusercontent.com/DataDog/datadog-api-client-python/master/.generator/schemas/v2/openapi.yaml",
1000
+ auth: { type: "api-key", header: "DD-API-KEY", key: "${DATADOG_API_KEY}" },
1001
+ tags: ["devops", "monitoring"]
1002
+ }
1003
+ ];
1004
+ var DATABASE_APIS = [
1005
+ {
1006
+ domain: "api.supabase.com",
1007
+ specUrl: "https://raw.githubusercontent.com/supabase/supabase/master/apps/docs/spec/api_v1_openapi.json",
1008
+ auth: { type: "bearer", token: "${SUPABASE_ACCESS_TOKEN}" },
1009
+ tags: ["database", "baas"]
1010
+ },
1011
+ {
1012
+ domain: "api.turso.tech",
1013
+ specUrl: "https://raw.githubusercontent.com/tursodatabase/turso-docs/main/api-reference/openapi.json",
1014
+ auth: { type: "bearer", token: "${TURSO_API_TOKEN}" },
1015
+ tags: ["database", "edge"]
1016
+ },
1017
+ {
1018
+ domain: "console.neon.tech",
1019
+ specUrl: "https://raw.githubusercontent.com/neondatabase/neon-api-python/main/v2.json",
1020
+ auth: { type: "bearer", token: "${NEON_API_KEY}" },
1021
+ tags: ["database", "serverless-postgres"]
1022
+ }
1023
+ ];
1024
+ var COMMERCE_APIS = [
1025
+ {
1026
+ domain: "api.stripe.com",
1027
+ specUrl: "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json",
1028
+ auth: { type: "bearer", token: "${STRIPE_SECRET_KEY}" },
1029
+ tags: ["commerce", "payments"]
1030
+ }
1031
+ ];
1032
+ var COMMUNICATION_APIS = [
1033
+ {
1034
+ domain: "slack.com",
1035
+ specUrl: "https://raw.githubusercontent.com/slackapi/slack-api-specs/master/web-api/slack_web_openapi_v2.json",
1036
+ auth: { type: "bearer", token: "${SLACK_BOT_TOKEN}" },
1037
+ tags: ["communication", "messaging"]
1038
+ },
1039
+ {
1040
+ domain: "discord.com",
1041
+ specUrl: "https://raw.githubusercontent.com/discord/discord-api-spec/main/specs/openapi.json",
1042
+ auth: { type: "bearer", token: "${DISCORD_BOT_TOKEN}", prefix: "Bot" },
1043
+ tags: ["communication", "messaging"]
1044
+ },
1045
+ {
1046
+ domain: "api.twilio.com",
1047
+ specUrl: "https://raw.githubusercontent.com/twilio/twilio-oai/main/spec/json/twilio_api_v2010.json",
1048
+ auth: { type: "basic", token: "${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}" },
1049
+ tags: ["communication", "sms", "voice"]
1050
+ },
1051
+ {
1052
+ domain: "api.resend.com",
1053
+ specUrl: "https://raw.githubusercontent.com/resendlabs/resend-openapi/main/resend.yaml",
1054
+ auth: { type: "bearer", token: "${RESEND_API_KEY}" },
1055
+ tags: ["communication", "email"]
1056
+ }
1057
+ ];
1058
+ var EVM_METHODS = [
1059
+ { rpcMethod: "eth_blockNumber", description: "Returns the latest block number", resource: "blocks", fsOp: "list" },
1060
+ { rpcMethod: "eth_getBlockByNumber", description: "Returns block by number", resource: "blocks", fsOp: "read" },
1061
+ { rpcMethod: "eth_getBalance", description: "Returns account balance in wei", resource: "balances", fsOp: "read" },
1062
+ { rpcMethod: "eth_getTransactionByHash", description: "Returns transaction by hash", resource: "transactions", fsOp: "read" },
1063
+ { rpcMethod: "eth_getTransactionReceipt", description: "Returns transaction receipt", resource: "receipts", fsOp: "read" },
1064
+ { rpcMethod: "eth_call", description: "Executes a call without creating a transaction", resource: "calls", fsOp: "read" },
1065
+ { rpcMethod: "eth_estimateGas", description: "Estimates gas needed for a transaction", resource: "gas", fsOp: "read" },
1066
+ { rpcMethod: "eth_gasPrice", description: "Returns current gas price in wei" },
1067
+ { rpcMethod: "eth_chainId", description: "Returns the chain ID" },
1068
+ { rpcMethod: "eth_getCode", description: "Returns contract bytecode at address", resource: "code", fsOp: "read" },
1069
+ { rpcMethod: "eth_getLogs", description: "Returns logs matching a filter", resource: "logs", fsOp: "list" },
1070
+ { rpcMethod: "eth_getTransactionCount", description: "Returns the number of transactions sent from an address", resource: "nonces", fsOp: "read" },
1071
+ { rpcMethod: "net_version", description: "Returns the network ID" }
1072
+ ];
1073
+ var RPC_APIS = [
1074
+ // ── Free / Public RPC Providers ──────────────────────────────────
1075
+ {
1076
+ domain: "rpc.ankr.com",
1077
+ rpcDef: { url: "https://rpc.ankr.com/eth", convention: "evm", methods: EVM_METHODS },
1078
+ tags: ["blockchain", "ethereum", "free"]
1079
+ },
1080
+ {
1081
+ domain: "cloudflare-eth.com",
1082
+ rpcDef: { url: "https://cloudflare-eth.com", convention: "evm", methods: EVM_METHODS },
1083
+ tags: ["blockchain", "ethereum", "free"]
1084
+ },
1085
+ {
1086
+ domain: "ethereum-rpc.publicnode.com",
1087
+ rpcDef: { url: "https://ethereum-rpc.publicnode.com", convention: "evm", methods: EVM_METHODS },
1088
+ tags: ["blockchain", "ethereum", "free"]
1089
+ },
1090
+ // ── Auth-Required RPC Providers ──────────────────────────────────
1091
+ {
1092
+ domain: "eth-mainnet.g.alchemy.com",
1093
+ rpcDef: { url: "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}", convention: "evm", methods: EVM_METHODS },
1094
+ tags: ["blockchain", "ethereum"]
1095
+ },
1096
+ {
1097
+ domain: "mainnet.infura.io",
1098
+ rpcDef: { url: "https://mainnet.infura.io/v3/${INFURA_API_KEY}", convention: "evm", methods: EVM_METHODS },
1099
+ tags: ["blockchain", "ethereum"]
1100
+ },
1101
+ // ── L2 / Alt Chains (Free) ───────────────────────────────────────
1102
+ {
1103
+ domain: "arb1.arbitrum.io",
1104
+ rpcDef: { url: "https://arb1.arbitrum.io/rpc", convention: "evm", methods: EVM_METHODS },
1105
+ tags: ["blockchain", "arbitrum", "l2", "free"]
1106
+ },
1107
+ {
1108
+ domain: "mainnet.optimism.io",
1109
+ rpcDef: { url: "https://mainnet.optimism.io", convention: "evm", methods: EVM_METHODS },
1110
+ tags: ["blockchain", "optimism", "l2", "free"]
1111
+ },
1112
+ {
1113
+ domain: "mainnet.base.org",
1114
+ rpcDef: { url: "https://mainnet.base.org", convention: "evm", methods: EVM_METHODS },
1115
+ tags: ["blockchain", "base", "l2", "free"]
1116
+ },
1117
+ {
1118
+ domain: "polygon-rpc.com",
1119
+ rpcDef: { url: "https://polygon-rpc.com", convention: "evm", methods: EVM_METHODS },
1120
+ tags: ["blockchain", "polygon", "free"]
1121
+ }
1122
+ ];
1123
+ var ALL_APIS = [
1124
+ ...FREE_APIS,
1125
+ ...FREEMIUM_APIS,
1126
+ ...DEVELOPER_TOOL_APIS,
1127
+ ...AI_APIS,
1128
+ ...CLOUD_APIS,
1129
+ ...PRODUCTIVITY_APIS,
1130
+ ...DEVOPS_APIS,
1131
+ ...DATABASE_APIS,
1132
+ ...COMMERCE_APIS,
1133
+ ...COMMUNICATION_APIS,
1134
+ ...RPC_APIS
1135
+ ];
1136
+
1137
+ // src/d1/sqlite-adapter.ts
1138
+ var BoundStatement = class {
1139
+ constructor(db, sql) {
1140
+ this.db = db;
1141
+ this.sql = sql;
1142
+ }
1143
+ params = [];
1144
+ bind(...values) {
1145
+ this.params = values;
1146
+ return this;
1147
+ }
1148
+ async first() {
1149
+ const stmt = this.db.prepare(this.sql);
1150
+ const row = stmt.get(...this.params);
1151
+ return row ?? null;
1152
+ }
1153
+ async all() {
1154
+ const stmt = this.db.prepare(this.sql);
1155
+ const rows = stmt.all(...this.params);
1156
+ return { results: rows, success: true };
1157
+ }
1158
+ async run() {
1159
+ const stmt = this.db.prepare(this.sql);
1160
+ const info = stmt.run(...this.params);
1161
+ return {
1162
+ success: true,
1163
+ changes: info.changes,
1164
+ lastRowId: Number(info.lastInsertRowid)
1165
+ };
1166
+ }
1167
+ };
1168
+ function createSqliteD1(db) {
1169
+ return {
1170
+ prepare(sql) {
1171
+ return new BoundStatement(db, sql);
1172
+ },
1173
+ async exec(sql) {
1174
+ db.exec(sql);
1175
+ }
1176
+ };
1177
+ }
1178
+
1179
+ // src/federation/d1-peer-store.ts
1180
+ function rowToPeer(row) {
1181
+ return {
1182
+ id: row.id,
1183
+ name: row.name,
1184
+ url: row.url,
1185
+ sharedSecret: row.shared_secret,
1186
+ status: row.status,
1187
+ advertisedDomains: JSON.parse(row.advertised_domains),
1188
+ lastSeen: row.last_seen,
1189
+ createdAt: row.created_at
1190
+ };
1191
+ }
1192
+ function rowToRule(row) {
1193
+ return {
1194
+ domain: row.domain,
1195
+ allow: row.allow === 1,
1196
+ peers: JSON.parse(row.peers),
1197
+ pricing: JSON.parse(row.pricing),
1198
+ ...row.rate_limit ? { rateLimit: JSON.parse(row.rate_limit) } : {},
1199
+ createdAt: row.created_at,
1200
+ updatedAt: row.updated_at
1201
+ };
1202
+ }
1203
+ var D1PeerStore = class {
1204
+ constructor(db) {
1205
+ this.db = db;
1206
+ }
1207
+ async initSchema() {
1208
+ await this.db.exec(`
1209
+ CREATE TABLE IF NOT EXISTS peers (
1210
+ id TEXT PRIMARY KEY,
1211
+ name TEXT NOT NULL,
1212
+ url TEXT NOT NULL,
1213
+ shared_secret TEXT NOT NULL,
1214
+ status TEXT NOT NULL DEFAULT 'active',
1215
+ advertised_domains TEXT NOT NULL DEFAULT '[]',
1216
+ last_seen INTEGER NOT NULL,
1217
+ created_at INTEGER NOT NULL
1218
+ )`);
1219
+ await this.db.exec(`
1220
+ CREATE TABLE IF NOT EXISTS lending_rules (
1221
+ domain TEXT PRIMARY KEY,
1222
+ allow INTEGER NOT NULL DEFAULT 1,
1223
+ peers TEXT NOT NULL DEFAULT '"*"',
1224
+ pricing TEXT NOT NULL DEFAULT '{"mode":"free"}',
1225
+ rate_limit TEXT,
1226
+ created_at INTEGER NOT NULL,
1227
+ updated_at INTEGER NOT NULL
1228
+ )`);
1229
+ }
1230
+ async getPeer(id) {
1231
+ const row = await this.db.prepare("SELECT * FROM peers WHERE id = ?").bind(id).first();
1232
+ return row ? rowToPeer(row) : null;
1233
+ }
1234
+ async putPeer(peer) {
1235
+ await this.db.prepare(
1236
+ `INSERT OR REPLACE INTO peers (id, name, url, shared_secret, status, advertised_domains, last_seen, created_at)
1237
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
1238
+ ).bind(
1239
+ peer.id,
1240
+ peer.name,
1241
+ peer.url,
1242
+ peer.sharedSecret,
1243
+ peer.status,
1244
+ JSON.stringify(peer.advertisedDomains),
1245
+ peer.lastSeen,
1246
+ peer.createdAt
1247
+ ).run();
1248
+ }
1249
+ async deletePeer(id) {
1250
+ await this.db.prepare("DELETE FROM peers WHERE id = ?").bind(id).run();
1251
+ }
1252
+ async listPeers() {
1253
+ const { results } = await this.db.prepare("SELECT * FROM peers WHERE status = 'active'").all();
1254
+ return results.map(rowToPeer);
1255
+ }
1256
+ async updateLastSeen(id, timestamp) {
1257
+ await this.db.prepare("UPDATE peers SET last_seen = ? WHERE id = ?").bind(timestamp, id).run();
1258
+ }
1259
+ async getRule(domain) {
1260
+ const row = await this.db.prepare("SELECT * FROM lending_rules WHERE domain = ?").bind(domain).first();
1261
+ return row ? rowToRule(row) : null;
1262
+ }
1263
+ async putRule(rule) {
1264
+ await this.db.prepare(
1265
+ `INSERT OR REPLACE INTO lending_rules (domain, allow, peers, pricing, rate_limit, created_at, updated_at)
1266
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
1267
+ ).bind(
1268
+ rule.domain,
1269
+ rule.allow ? 1 : 0,
1270
+ JSON.stringify(rule.peers),
1271
+ JSON.stringify(rule.pricing),
1272
+ rule.rateLimit ? JSON.stringify(rule.rateLimit) : null,
1273
+ rule.createdAt,
1274
+ rule.updatedAt
1275
+ ).run();
1276
+ }
1277
+ async deleteRule(domain) {
1278
+ await this.db.prepare("DELETE FROM lending_rules WHERE domain = ?").bind(domain).run();
1279
+ }
1280
+ async listRules() {
1281
+ const { results } = await this.db.prepare("SELECT * FROM lending_rules").all();
1282
+ return results.map(rowToRule);
1283
+ }
1284
+ };
1285
+
1286
+ // src/tunnel/memory-store.ts
1287
+ var MemoryTunnelStore = class {
1288
+ records = /* @__PURE__ */ new Map();
1289
+ async get(id) {
1290
+ return this.records.get(id) ?? null;
1291
+ }
1292
+ async getByAgent(agentId) {
1293
+ for (const record of this.records.values()) {
1294
+ if (record.agentId === agentId && record.status === "active") {
1295
+ return record;
1296
+ }
1297
+ }
1298
+ return null;
1299
+ }
1300
+ async put(record) {
1301
+ this.records.set(record.id, record);
1302
+ }
1303
+ async delete(id) {
1304
+ this.records.delete(id);
1305
+ }
1306
+ async list() {
1307
+ return Array.from(this.records.values());
1308
+ }
1309
+ };
1310
+
1311
+ // src/tunnel/cloudflare-provider.ts
1312
+ var CF_API = "https://api.cloudflare.com/client/v4";
1313
+ var CloudflareTunnelProvider = class {
1314
+ constructor(accountId, apiToken, tunnelDomain, zoneId) {
1315
+ this.accountId = accountId;
1316
+ this.apiToken = apiToken;
1317
+ this.tunnelDomain = tunnelDomain;
1318
+ this.zoneId = zoneId;
1319
+ }
1320
+ async create(name, hostname) {
1321
+ const secretBytes = new Uint8Array(32);
1322
+ crypto.getRandomValues(secretBytes);
1323
+ const tunnelSecret = btoa(String.fromCharCode(...secretBytes));
1324
+ const tunnelRes = await this.cfFetch(
1325
+ `/accounts/${this.accountId}/cfd_tunnel`,
1326
+ {
1327
+ method: "POST",
1328
+ body: JSON.stringify({
1329
+ name,
1330
+ tunnel_secret: tunnelSecret
1331
+ })
1332
+ }
1333
+ );
1334
+ const tunnelId = tunnelRes.id;
1335
+ try {
1336
+ await this.cfFetch(`/zones/${this.zoneId}/dns_records`, {
1337
+ method: "POST",
1338
+ body: JSON.stringify({
1339
+ type: "CNAME",
1340
+ name: hostname,
1341
+ content: `${tunnelId}.cfargotunnel.com`,
1342
+ proxied: true
1343
+ })
1344
+ });
1345
+ await this.cfFetch(
1346
+ `/accounts/${this.accountId}/cfd_tunnel/${tunnelId}/configurations`,
1347
+ {
1348
+ method: "PUT",
1349
+ body: JSON.stringify({
1350
+ config: {
1351
+ ingress: [
1352
+ { hostname, service: "http://localhost:9090" },
1353
+ { service: "http_status:404" }
1354
+ ]
1355
+ }
1356
+ })
1357
+ }
1358
+ );
1359
+ const tokenRes = await this.cfFetch(
1360
+ `/accounts/${this.accountId}/cfd_tunnel/${tunnelId}/token`
1361
+ );
1362
+ return { tunnelId, tunnelToken: tokenRes };
1363
+ } catch (err) {
1364
+ await this.cfFetch(
1365
+ `/accounts/${this.accountId}/cfd_tunnel/${tunnelId}?cascade=true`,
1366
+ { method: "DELETE" }
1367
+ ).catch(() => {
1368
+ });
1369
+ throw err;
1370
+ }
1371
+ }
1372
+ async delete(tunnelId) {
1373
+ const dnsRecords = await this.cfFetch(
1374
+ `/zones/${this.zoneId}/dns_records?type=CNAME&content=${tunnelId}.cfargotunnel.com`
1375
+ );
1376
+ for (const record of dnsRecords) {
1377
+ await this.cfFetch(`/zones/${this.zoneId}/dns_records/${record.id}`, {
1378
+ method: "DELETE"
1379
+ });
1380
+ }
1381
+ await this.cfFetch(
1382
+ `/accounts/${this.accountId}/cfd_tunnel/${tunnelId}?cascade=true`,
1383
+ { method: "DELETE" }
1384
+ );
1385
+ }
1386
+ async cfFetch(path, init) {
1387
+ const res = await fetch(`${CF_API}${path}`, {
1388
+ ...init,
1389
+ headers: {
1390
+ Authorization: `Bearer ${this.apiToken}`,
1391
+ "Content-Type": "application/json",
1392
+ ...init?.headers
1393
+ }
1394
+ });
1395
+ const data = await res.json();
1396
+ if (!data.success) {
1397
+ const msg = data.errors.map((e) => e.message).join(", ");
1398
+ throw new Error(`Cloudflare API error: ${msg}`);
1399
+ }
1400
+ return data.result;
1401
+ }
1402
+ };
1403
+
1404
+ // src/index.ts
1405
+ var VERSION = "0.1.0";
1406
+ export {
1407
+ CloudflareTunnelProvider,
1408
+ Context7Backend,
1409
+ Context7Client,
1410
+ D1CredentialVault,
1411
+ D1MeterStore,
1412
+ D1PeerStore,
1413
+ D1RegistryStore,
1414
+ MemoryCredentialVault,
1415
+ MemoryMeterStore,
1416
+ MemoryRegistryStore,
1417
+ MemoryTunnelStore,
1418
+ OnboardPipeline,
1419
+ VERSION,
1420
+ VirtualFileBackend,
1421
+ checkAccess,
1422
+ createRegistryResolver,
1423
+ createSqliteD1,
1424
+ credentialRoutes,
1425
+ discoverFromApisGuru,
1426
+ extractDomainPath,
1427
+ lookupPricing,
1428
+ meter,
1429
+ parsePricingAnnotation,
1430
+ parseSkillMd,
1431
+ queryDnsTxt,
1432
+ skillToHttpConfig,
1433
+ tunnelRoutes
1434
+ };