@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.cjs ADDED
@@ -0,0 +1,2436 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ CloudflareTunnelProvider: () => CloudflareTunnelProvider,
34
+ Context7Backend: () => Context7Backend,
35
+ Context7Client: () => Context7Client,
36
+ D1CredentialVault: () => D1CredentialVault,
37
+ D1MeterStore: () => D1MeterStore,
38
+ D1PeerStore: () => D1PeerStore,
39
+ D1RegistryStore: () => D1RegistryStore,
40
+ MemoryCredentialVault: () => MemoryCredentialVault,
41
+ MemoryMeterStore: () => MemoryMeterStore,
42
+ MemoryRegistryStore: () => MemoryRegistryStore,
43
+ MemoryTunnelStore: () => MemoryTunnelStore,
44
+ OnboardPipeline: () => OnboardPipeline,
45
+ VERSION: () => VERSION,
46
+ VirtualFileBackend: () => VirtualFileBackend,
47
+ checkAccess: () => checkAccess,
48
+ createRegistryResolver: () => createRegistryResolver,
49
+ createSqliteD1: () => createSqliteD1,
50
+ credentialRoutes: () => credentialRoutes,
51
+ discoverFromApisGuru: () => discoverFromApisGuru,
52
+ extractDomainPath: () => extractDomainPath,
53
+ lookupPricing: () => lookupPricing,
54
+ meter: () => meter,
55
+ parsePricingAnnotation: () => parsePricingAnnotation,
56
+ parseSkillMd: () => parseSkillMd,
57
+ queryDnsTxt: () => queryDnsTxt,
58
+ skillToHttpConfig: () => skillToHttpConfig,
59
+ tunnelRoutes: () => tunnelRoutes
60
+ });
61
+ module.exports = __toCommonJS(index_exports);
62
+
63
+ // src/registry/memory-store.ts
64
+ var MemoryRegistryStore = class {
65
+ // key = "domain@version"
66
+ records = /* @__PURE__ */ new Map();
67
+ key(domain, version) {
68
+ return `${domain}@${version}`;
69
+ }
70
+ async get(domain) {
71
+ for (const record of this.records.values()) {
72
+ if (record.domain === domain && record.isDefault) return record;
73
+ }
74
+ return null;
75
+ }
76
+ async getVersion(domain, version) {
77
+ return this.records.get(this.key(domain, version)) ?? null;
78
+ }
79
+ async listVersions(domain) {
80
+ const versions = [];
81
+ for (const record of this.records.values()) {
82
+ if (record.domain === domain) {
83
+ versions.push({
84
+ version: record.version,
85
+ status: record.status,
86
+ isDefault: record.isDefault,
87
+ createdAt: record.createdAt,
88
+ updatedAt: record.updatedAt
89
+ });
90
+ }
91
+ }
92
+ return versions.sort((a, b) => b.createdAt - a.createdAt);
93
+ }
94
+ async put(domain, record) {
95
+ this.records.set(this.key(domain, record.version), record);
96
+ }
97
+ async delete(domain) {
98
+ const keysToDelete = [];
99
+ for (const [key, record] of this.records.entries()) {
100
+ if (record.domain === domain) keysToDelete.push(key);
101
+ }
102
+ for (const key of keysToDelete) {
103
+ this.records.delete(key);
104
+ }
105
+ }
106
+ async list() {
107
+ const results = [];
108
+ for (const record of this.records.values()) {
109
+ if (record.isDefault) results.push(toSummary(record));
110
+ }
111
+ return results;
112
+ }
113
+ async search(query) {
114
+ const q = query.toLowerCase();
115
+ const results = [];
116
+ for (const record of this.records.values()) {
117
+ if (!record.isDefault) continue;
118
+ const nameMatch = record.name.toLowerCase().includes(q) || record.description.toLowerCase().includes(q);
119
+ const matched = record.endpoints.filter(
120
+ (e) => e.description.toLowerCase().includes(q) || e.method.toLowerCase().includes(q) || e.path.toLowerCase().includes(q)
121
+ );
122
+ if (nameMatch || matched.length > 0) {
123
+ results.push({
124
+ ...toSummary(record),
125
+ matchedEndpoints: matched.map((e) => ({
126
+ method: e.method,
127
+ path: e.path,
128
+ description: e.description
129
+ }))
130
+ });
131
+ }
132
+ }
133
+ return results;
134
+ }
135
+ };
136
+ function toSummary(record) {
137
+ return {
138
+ domain: record.domain,
139
+ name: record.name,
140
+ description: record.description,
141
+ isFirstParty: record.isFirstParty
142
+ };
143
+ }
144
+
145
+ // src/registry/d1-store.ts
146
+ var CREATE_SERVICES = `
147
+ CREATE TABLE IF NOT EXISTS services (
148
+ domain TEXT NOT NULL,
149
+ version TEXT NOT NULL,
150
+ name TEXT NOT NULL,
151
+ description TEXT,
152
+ roles TEXT,
153
+ skill_md TEXT NOT NULL,
154
+ endpoints TEXT,
155
+ is_first_party INTEGER DEFAULT 0,
156
+ status TEXT DEFAULT 'active',
157
+ is_default INTEGER DEFAULT 1,
158
+ source TEXT,
159
+ sunset_date INTEGER,
160
+ auth_mode TEXT,
161
+ created_at INTEGER NOT NULL,
162
+ updated_at INTEGER NOT NULL,
163
+ PRIMARY KEY (domain, version)
164
+ )`;
165
+ var CREATE_SERVICES_DEFAULT_INDEX = `
166
+ CREATE INDEX IF NOT EXISTS idx_services_default ON services(domain, is_default)`;
167
+ var CREATE_DOMAIN_CHALLENGES = `
168
+ CREATE TABLE IF NOT EXISTS domain_challenges (
169
+ domain TEXT PRIMARY KEY,
170
+ challenge_code TEXT NOT NULL,
171
+ status TEXT NOT NULL DEFAULT 'pending',
172
+ created_at INTEGER NOT NULL,
173
+ verified_at INTEGER,
174
+ expires_at INTEGER NOT NULL
175
+ )`;
176
+ function rowToRecord(row) {
177
+ return {
178
+ domain: row.domain,
179
+ name: row.name,
180
+ description: row.description,
181
+ version: row.version,
182
+ roles: JSON.parse(row.roles),
183
+ skillMd: row.skill_md,
184
+ endpoints: JSON.parse(row.endpoints),
185
+ isFirstParty: row.is_first_party === 1,
186
+ createdAt: row.created_at,
187
+ updatedAt: row.updated_at,
188
+ status: row.status,
189
+ isDefault: row.is_default === 1,
190
+ ...row.source ? { source: JSON.parse(row.source) } : {},
191
+ ...row.sunset_date ? { sunsetDate: row.sunset_date } : {},
192
+ ...row.auth_mode ? { authMode: row.auth_mode } : {}
193
+ };
194
+ }
195
+ function toSummary2(row) {
196
+ return {
197
+ domain: row.domain,
198
+ name: row.name,
199
+ description: row.description,
200
+ isFirstParty: row.is_first_party === 1
201
+ };
202
+ }
203
+ var D1RegistryStore = class _D1RegistryStore {
204
+ constructor(db) {
205
+ this.db = db;
206
+ }
207
+ async initSchema() {
208
+ await this.db.exec(CREATE_SERVICES);
209
+ await this.db.exec(CREATE_SERVICES_DEFAULT_INDEX);
210
+ await this.db.exec(CREATE_DOMAIN_CHALLENGES);
211
+ }
212
+ async get(domain) {
213
+ const row = await this.db.prepare("SELECT * FROM services WHERE domain = ? AND is_default = 1").bind(domain).first();
214
+ return row ? rowToRecord(row) : null;
215
+ }
216
+ async getVersion(domain, version) {
217
+ const row = await this.db.prepare("SELECT * FROM services WHERE domain = ? AND version = ?").bind(domain, version).first();
218
+ return row ? rowToRecord(row) : null;
219
+ }
220
+ async listVersions(domain) {
221
+ const { results } = await this.db.prepare(
222
+ "SELECT version, status, is_default, created_at, updated_at FROM services WHERE domain = ? ORDER BY created_at DESC"
223
+ ).bind(domain).all();
224
+ return results.map((row) => ({
225
+ version: row.version,
226
+ status: row.status,
227
+ isDefault: row.is_default === 1,
228
+ createdAt: row.created_at,
229
+ updatedAt: row.updated_at
230
+ }));
231
+ }
232
+ /** Max endpoints JSON size before stripping verbose fields (parameters, requestBody, responses). */
233
+ static ENDPOINTS_SIZE_LIMIT = 8e5;
234
+ // ~800 KB, well under D1's ~1 MB per-value limit
235
+ async put(domain, record) {
236
+ let endpointsJson = JSON.stringify(record.endpoints);
237
+ if (endpointsJson.length > _D1RegistryStore.ENDPOINTS_SIZE_LIMIT) {
238
+ const slim = record.endpoints.map(({ method, path, description, price, pricing }) => ({
239
+ method,
240
+ path,
241
+ description,
242
+ ...price ? { price } : {},
243
+ ...pricing ? { pricing } : {}
244
+ }));
245
+ endpointsJson = JSON.stringify(slim);
246
+ }
247
+ await this.db.prepare(
248
+ `INSERT OR REPLACE INTO services
249
+ (domain, version, name, description, roles, skill_md, endpoints, is_first_party, status, is_default, source, sunset_date, auth_mode, created_at, updated_at)
250
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
251
+ ).bind(
252
+ domain,
253
+ record.version,
254
+ record.name,
255
+ record.description,
256
+ JSON.stringify(record.roles),
257
+ record.skillMd,
258
+ endpointsJson,
259
+ record.isFirstParty ? 1 : 0,
260
+ record.status,
261
+ record.isDefault ? 1 : 0,
262
+ record.source ? JSON.stringify(record.source) : null,
263
+ record.sunsetDate ?? null,
264
+ record.authMode ?? null,
265
+ record.createdAt,
266
+ record.updatedAt
267
+ ).run();
268
+ }
269
+ async delete(domain) {
270
+ await this.db.prepare("DELETE FROM services WHERE domain = ?").bind(domain).run();
271
+ }
272
+ async list() {
273
+ const { results } = await this.db.prepare("SELECT domain, name, description, is_first_party FROM services WHERE is_default = 1").all();
274
+ return results.map(toSummary2);
275
+ }
276
+ async search(query) {
277
+ const pattern = `%${query}%`;
278
+ const { results: rows } = await this.db.prepare(
279
+ `SELECT * FROM services
280
+ WHERE is_default = 1 AND (name LIKE ? OR description LIKE ? OR endpoints LIKE ?)`
281
+ ).bind(pattern, pattern, pattern).all();
282
+ const q = query.toLowerCase();
283
+ return rows.map((row) => {
284
+ const endpoints = JSON.parse(row.endpoints);
285
+ const matched = endpoints.filter(
286
+ (e) => e.description.toLowerCase().includes(q) || e.method.toLowerCase().includes(q) || e.path.toLowerCase().includes(q)
287
+ );
288
+ return {
289
+ ...toSummary2(row),
290
+ matchedEndpoints: matched.map((e) => ({
291
+ method: e.method,
292
+ path: e.path,
293
+ description: e.description
294
+ }))
295
+ };
296
+ });
297
+ }
298
+ async stats() {
299
+ const row = await this.db.prepare(
300
+ `SELECT COUNT(*) as service_count, COALESCE(SUM(json_array_length(endpoints)), 0) as endpoint_count
301
+ FROM services WHERE is_default = 1`
302
+ ).first();
303
+ return {
304
+ serviceCount: row?.service_count ?? 0,
305
+ endpointCount: row?.endpoint_count ?? 0
306
+ };
307
+ }
308
+ };
309
+
310
+ // src/registry/skill-parser.ts
311
+ var import_yaml = require("yaml");
312
+ function parseSkillMd(domain, raw, options) {
313
+ const { frontmatter, body } = extractFrontmatter(raw);
314
+ const parsed = (0, import_yaml.parse)(frontmatter) ?? {};
315
+ const description = extractDescription(body);
316
+ const endpoints = extractEndpoints(body);
317
+ const now = Date.now();
318
+ return {
319
+ domain,
320
+ name: parsed.name ?? domain,
321
+ description,
322
+ version: parsed.version ?? "0.0",
323
+ roles: parsed.roles ?? ["agent"],
324
+ skillMd: raw,
325
+ endpoints,
326
+ isFirstParty: options?.isFirstParty ?? false,
327
+ createdAt: now,
328
+ updatedAt: now,
329
+ status: "active",
330
+ isDefault: true
331
+ };
332
+ }
333
+ function parsePricingAnnotation(text) {
334
+ const match = text.match(
335
+ /(\d+(?:\.\d+)?)\s+(\w+)\s*\/\s*(call|byte|minute|次)/i
336
+ );
337
+ if (!match) return void 0;
338
+ return {
339
+ cost: parseFloat(match[1]),
340
+ currency: match[2].toUpperCase(),
341
+ per: match[3] === "\u6B21" ? "call" : match[3].toLowerCase()
342
+ };
343
+ }
344
+ function extractFrontmatter(raw) {
345
+ const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
346
+ if (!match) return { frontmatter: "", body: raw };
347
+ return { frontmatter: match[1], body: match[2] };
348
+ }
349
+ function extractDescription(body) {
350
+ const lines = body.split("\n");
351
+ let foundTitle = false;
352
+ const descLines = [];
353
+ for (const line of lines) {
354
+ if (!foundTitle) {
355
+ if (line.startsWith("# ")) foundTitle = true;
356
+ continue;
357
+ }
358
+ if (line.startsWith("## ")) break;
359
+ const trimmed = line.trim();
360
+ if (trimmed === "" && descLines.length > 0) break;
361
+ if (trimmed !== "") descLines.push(trimmed);
362
+ }
363
+ return descLines.join(" ");
364
+ }
365
+ function extractEndpoints(body) {
366
+ const endpoints = [];
367
+ const lines = body.split("\n");
368
+ let inApiSection = false;
369
+ let currentHeading = null;
370
+ for (const line of lines) {
371
+ if (line.startsWith("## API")) {
372
+ inApiSection = true;
373
+ continue;
374
+ }
375
+ if (inApiSection && line.startsWith("## ") && !line.startsWith("## API")) {
376
+ break;
377
+ }
378
+ if (!inApiSection) continue;
379
+ if (line.startsWith("### ")) {
380
+ currentHeading = line.slice(4).trim();
381
+ continue;
382
+ }
383
+ const endpointMatch = line.match(/^`(GET|POST|PUT|PATCH|DELETE)\s+(\S+)`/);
384
+ if (endpointMatch && currentHeading) {
385
+ const afterBacktick = line.slice(line.indexOf("`", 1) + 1).trim();
386
+ const pricing = afterBacktick.startsWith("\u2014") ? parsePricingAnnotation(afterBacktick.slice(1).trim()) : void 0;
387
+ endpoints.push({
388
+ method: endpointMatch[1],
389
+ path: endpointMatch[2],
390
+ description: currentHeading,
391
+ ...pricing ? { pricing } : {}
392
+ });
393
+ currentHeading = null;
394
+ }
395
+ }
396
+ return endpoints;
397
+ }
398
+
399
+ // src/registry/skill-to-config.ts
400
+ function skillToHttpConfig(record) {
401
+ let baseUrl = `https://${record.domain}`;
402
+ if (record.source?.basePath) {
403
+ baseUrl += record.source.basePath;
404
+ }
405
+ const resources = extractResources(record.skillMd);
406
+ const endpoints = extractHttpEndpoints(record.skillMd);
407
+ return {
408
+ baseUrl,
409
+ resources,
410
+ endpoints
411
+ };
412
+ }
413
+ function extractResources(skillMd) {
414
+ const resources = [];
415
+ const lines = skillMd.split("\n");
416
+ let inSchema = false;
417
+ let current = null;
418
+ for (const line of lines) {
419
+ if (line.startsWith("## Schema")) {
420
+ inSchema = true;
421
+ continue;
422
+ }
423
+ if (inSchema && line.startsWith("## ") && !line.startsWith("## Schema")) {
424
+ break;
425
+ }
426
+ if (!inSchema) continue;
427
+ const tableMatch = line.match(/^### (\w+)\s/);
428
+ if (tableMatch) {
429
+ if (current) resources.push(toHttpResource(current));
430
+ current = { name: tableMatch[1], fields: [] };
431
+ continue;
432
+ }
433
+ if (current && line.startsWith("|") && !line.startsWith("|--") && !line.startsWith("| field")) {
434
+ const cells = line.split("|").map((c) => c.trim()).filter(Boolean);
435
+ if (cells.length >= 3) {
436
+ current.fields.push({
437
+ name: cells[0],
438
+ type: cells[1],
439
+ description: cells[2]
440
+ });
441
+ }
442
+ }
443
+ }
444
+ if (current) resources.push(toHttpResource(current));
445
+ return resources;
446
+ }
447
+ function toHttpResource(parsed) {
448
+ return {
449
+ name: parsed.name,
450
+ apiPath: `/${parsed.name}`,
451
+ fields: parsed.fields
452
+ };
453
+ }
454
+ function extractHttpEndpoints(skillMd) {
455
+ const endpoints = [];
456
+ const lines = skillMd.split("\n");
457
+ let inApi = false;
458
+ let currentHeading = null;
459
+ for (const line of lines) {
460
+ if (line.startsWith("## API")) {
461
+ inApi = true;
462
+ continue;
463
+ }
464
+ if (inApi && line.startsWith("## ") && !line.startsWith("## API")) {
465
+ break;
466
+ }
467
+ if (!inApi) continue;
468
+ if (line.startsWith("### ")) {
469
+ currentHeading = line.slice(4).trim();
470
+ continue;
471
+ }
472
+ const match = line.match(/^`(GET|POST|PUT|PATCH|DELETE)\s+(\S+)`/);
473
+ if (match && currentHeading) {
474
+ const slug = currentHeading.toLowerCase().replace(/\s+/g, "-");
475
+ endpoints.push({
476
+ name: slug,
477
+ method: match[1],
478
+ apiPath: match[2],
479
+ description: currentHeading
480
+ });
481
+ currentHeading = null;
482
+ }
483
+ }
484
+ return endpoints;
485
+ }
486
+ function skillToRpcConfig(meta) {
487
+ const resources = meta.resources.map((r) => {
488
+ const methods = {};
489
+ const builder = getParamsBuilder(meta.convention);
490
+ for (const [fsOp, rpcMethod] of Object.entries(r.methods)) {
491
+ const key = fsOp;
492
+ methods[key] = {
493
+ method: rpcMethod,
494
+ params: builder(key)
495
+ };
496
+ }
497
+ const resource = {
498
+ name: r.name,
499
+ ...r.idField ? { idField: r.idField } : {},
500
+ methods
501
+ };
502
+ if (meta.convention === "evm") {
503
+ resource.transform = buildEvmTransforms(r.name);
504
+ }
505
+ return resource;
506
+ });
507
+ return { resources };
508
+ }
509
+ function getParamsBuilder(convention) {
510
+ switch (convention) {
511
+ case "crud":
512
+ return crudParamsBuilder;
513
+ case "evm":
514
+ return evmParamsBuilder;
515
+ case "raw":
516
+ default:
517
+ return rawParamsBuilder;
518
+ }
519
+ }
520
+ function rawParamsBuilder(_fsOp) {
521
+ return (ctx) => {
522
+ if (ctx.data !== void 0) return [ctx.data];
523
+ if (ctx.id !== void 0) return [ctx.id];
524
+ return [];
525
+ };
526
+ }
527
+ function crudParamsBuilder(fsOp) {
528
+ switch (fsOp) {
529
+ case "list":
530
+ return () => [];
531
+ case "read":
532
+ return (ctx) => [ctx.id];
533
+ case "write":
534
+ return (ctx) => [ctx.id, ctx.data];
535
+ case "create":
536
+ return (ctx) => [ctx.data];
537
+ case "remove":
538
+ return (ctx) => [ctx.id];
539
+ case "search":
540
+ return (ctx) => [ctx.pattern];
541
+ default:
542
+ return () => [];
543
+ }
544
+ }
545
+ function evmParamsBuilder(fsOp) {
546
+ switch (fsOp) {
547
+ case "list":
548
+ return () => [];
549
+ case "read":
550
+ return (ctx) => {
551
+ const id = ctx.id;
552
+ const hexId = /^\d+$/.test(id) ? "0x" + Number(id).toString(16) : id;
553
+ return [hexId, "latest"];
554
+ };
555
+ default:
556
+ return rawParamsBuilder(fsOp);
557
+ }
558
+ }
559
+ function buildEvmTransforms(resourceName) {
560
+ const transform = {};
561
+ if (resourceName === "blocks") {
562
+ transform.list = (data) => {
563
+ const hex = String(data);
564
+ const latest = parseInt(hex, 16);
565
+ if (isNaN(latest)) return [];
566
+ return Array.from({ length: 10 }, (_, i) => `${latest - i}.json`);
567
+ };
568
+ }
569
+ if (resourceName === "balances") {
570
+ transform.read = (data) => {
571
+ const hex = String(data);
572
+ return { wei: hex, raw: hex };
573
+ };
574
+ }
575
+ return Object.keys(transform).length > 0 ? transform : void 0;
576
+ }
577
+
578
+ // src/registry/resolver.ts
579
+ var import_agent_fs = require("@nkmc/agent-fs");
580
+ var import_core = require("@nkmc/core");
581
+
582
+ // src/registry/virtual-files.ts
583
+ var VIRTUAL_FILES = ["_pricing.json", "_versions.json", "skill.md"];
584
+ var VirtualFileBackend = class {
585
+ inner;
586
+ domain;
587
+ store;
588
+ constructor(options) {
589
+ this.inner = options.inner;
590
+ this.domain = options.domain;
591
+ this.store = options.store;
592
+ }
593
+ async list(path) {
594
+ const entries = await this.inner.list(path);
595
+ if (path === "/" || path === "" || path === ".") {
596
+ return [...entries, ...VIRTUAL_FILES];
597
+ }
598
+ return entries;
599
+ }
600
+ async read(path) {
601
+ const cleaned = path.replace(/^\/+/, "").replace(/\/+$/, "");
602
+ if (cleaned === "_pricing.json") {
603
+ const record = await this.store.get(this.domain);
604
+ if (!record) return { endpoints: [] };
605
+ return {
606
+ domain: this.domain,
607
+ endpoints: record.endpoints.filter((ep) => ep.pricing).map((ep) => ({
608
+ method: ep.method,
609
+ path: ep.path,
610
+ description: ep.description,
611
+ pricing: ep.pricing
612
+ }))
613
+ };
614
+ }
615
+ if (cleaned === "_versions.json") {
616
+ const versions = await this.store.listVersions(this.domain);
617
+ return { domain: this.domain, versions };
618
+ }
619
+ if (cleaned === "skill.md") {
620
+ const record = await this.store.get(this.domain);
621
+ if (!record) return "# Not found\n";
622
+ return record.skillMd;
623
+ }
624
+ return this.inner.read(path);
625
+ }
626
+ async write(path, data) {
627
+ return this.inner.write(path, data);
628
+ }
629
+ async remove(path) {
630
+ return this.inner.remove(path);
631
+ }
632
+ async search(path, pattern) {
633
+ return this.inner.search(path, pattern);
634
+ }
635
+ };
636
+
637
+ // src/federation/peer-backend.ts
638
+ var PeerBackend = class {
639
+ constructor(client, peer, agentId) {
640
+ this.client = client;
641
+ this.peer = peer;
642
+ this.agentId = agentId;
643
+ }
644
+ async list(path) {
645
+ const result = await this.execOnPeer(`ls ${path}`);
646
+ return result.data ?? [];
647
+ }
648
+ async read(path) {
649
+ const result = await this.execOnPeer(`cat ${path}`);
650
+ return result.data;
651
+ }
652
+ async write(path, data) {
653
+ const result = await this.execOnPeer(
654
+ `write ${path} ${JSON.stringify(data)}`
655
+ );
656
+ return result.data ?? { id: "" };
657
+ }
658
+ async remove(path) {
659
+ await this.execOnPeer(`rm ${path}`);
660
+ }
661
+ async search(path, pattern) {
662
+ const result = await this.execOnPeer(`grep ${pattern} ${path}`);
663
+ return result.data ?? [];
664
+ }
665
+ async execOnPeer(command) {
666
+ const result = await this.client.exec(this.peer, {
667
+ command,
668
+ agentId: this.agentId
669
+ });
670
+ if (!result.ok) {
671
+ if (result.paymentRequired) {
672
+ throw new Error(
673
+ `Payment required: ${result.paymentRequired.price} ${result.paymentRequired.currency}`
674
+ );
675
+ }
676
+ throw new Error(result.error ?? "Peer execution failed");
677
+ }
678
+ return result;
679
+ }
680
+ };
681
+
682
+ // src/registry/resolver.ts
683
+ function createRegistryResolver(storeOrOptions) {
684
+ const options = "get" in storeOrOptions && "put" in storeOrOptions ? { store: storeOrOptions } : storeOrOptions;
685
+ const { store, vault, gatewayPrivateKey } = options;
686
+ const loaded = /* @__PURE__ */ new Set();
687
+ async function tryPeerFallback(domain, version, addMount, agent) {
688
+ if (!options.peerClient || !options.peerStore) return false;
689
+ const peers = await options.peerStore.listPeers();
690
+ for (const peer of peers) {
691
+ if (peer.advertisedDomains.length > 0 && !peer.advertisedDomains.includes(domain)) {
692
+ continue;
693
+ }
694
+ const result = await options.peerClient.query(peer, domain);
695
+ if (result.available) {
696
+ const peerBackend = new PeerBackend(options.peerClient, peer, agent?.id ?? "anonymous");
697
+ const mountPath = version ? `/${domain}@${version}` : `/${domain}`;
698
+ addMount({ path: mountPath, backend: peerBackend });
699
+ return true;
700
+ }
701
+ }
702
+ return false;
703
+ }
704
+ async function onMiss(path, addMount, agent) {
705
+ const { domain, version } = extractDomainPath(path);
706
+ if (!domain) return false;
707
+ const cacheKey = version ? `${domain}@${version}` : domain;
708
+ const record = version ? await store.getVersion(domain, version) : await store.get(domain);
709
+ if (!record) {
710
+ return tryPeerFallback(domain, version, addMount, agent);
711
+ }
712
+ const isNkmcJwt = record.authMode === "nkmc-jwt";
713
+ if (!isNkmcJwt && loaded.has(cacheKey)) return false;
714
+ if (record.status === "sunset") return false;
715
+ let auth;
716
+ if (isNkmcJwt && gatewayPrivateKey && agent) {
717
+ const token = await (0, import_core.signJwt)(gatewayPrivateKey, {
718
+ sub: agent.id,
719
+ roles: agent.roles,
720
+ svc: domain
721
+ }, { expiresIn: "5m" });
722
+ auth = { type: "bearer", token };
723
+ } else if (vault) {
724
+ const cred = await vault.get(domain, agent?.id);
725
+ if (cred) {
726
+ auth = cred.auth;
727
+ }
728
+ }
729
+ if (!auth && !isNkmcJwt) {
730
+ const peerMounted = await tryPeerFallback(domain, version, addMount, agent);
731
+ if (peerMounted) return true;
732
+ }
733
+ let backend;
734
+ if (record.source?.type === "jsonrpc" && record.source.rpc) {
735
+ const { resources } = skillToRpcConfig(record.source.rpc);
736
+ const headers = {};
737
+ if (auth) {
738
+ if (auth.type === "bearer") {
739
+ headers["Authorization"] = `${auth.prefix ?? "Bearer"} ${auth.token}`;
740
+ } else if (auth.type === "api-key") {
741
+ headers[auth.header] = auth.key;
742
+ }
743
+ }
744
+ const transport = new import_agent_fs.JsonRpcTransport({ url: record.source.rpc.rpcUrl, headers });
745
+ backend = new import_agent_fs.RpcBackend({ transport, resources });
746
+ } else {
747
+ const config = skillToHttpConfig(record);
748
+ config.auth = auth;
749
+ backend = new import_agent_fs.HttpBackend(config);
750
+ }
751
+ let finalBackend = backend;
752
+ if (options.wrapVirtualFiles !== false) {
753
+ finalBackend = new VirtualFileBackend({ inner: backend, domain, store: options.store });
754
+ }
755
+ const mountPath = version ? `/${domain}@${version}` : `/${domain}`;
756
+ addMount({ path: mountPath, backend: finalBackend });
757
+ if (!isNkmcJwt) {
758
+ loaded.add(cacheKey);
759
+ }
760
+ return true;
761
+ }
762
+ async function listDomains() {
763
+ const summaries = await store.list();
764
+ return summaries.map((s) => s.domain);
765
+ }
766
+ async function searchDomains(query) {
767
+ return store.search(query);
768
+ }
769
+ async function searchEndpoints(domain, query) {
770
+ const record = await store.get(domain);
771
+ if (!record) return [];
772
+ const q = query.toLowerCase();
773
+ return record.endpoints.filter(
774
+ (e) => e.description.toLowerCase().includes(q) || e.method.toLowerCase().includes(q) || e.path.toLowerCase().includes(q)
775
+ ).map((e) => ({ method: e.method, path: e.path, description: e.description }));
776
+ }
777
+ return { onMiss, listDomains, searchDomains, searchEndpoints };
778
+ }
779
+ function extractDomainPath(path) {
780
+ const segments = path.split("/").filter(Boolean);
781
+ if (segments.length === 0) return { domain: null, version: null };
782
+ const first = segments[0];
783
+ const atIndex = first.indexOf("@");
784
+ if (atIndex > 0) {
785
+ return {
786
+ domain: first.slice(0, atIndex),
787
+ version: first.slice(atIndex + 1)
788
+ };
789
+ }
790
+ return { domain: first, version: null };
791
+ }
792
+
793
+ // src/metering/memory-store.ts
794
+ var MemoryMeterStore = class {
795
+ records = [];
796
+ async record(entry) {
797
+ this.records.push(entry);
798
+ }
799
+ async query(filter) {
800
+ return this.records.filter((r) => this.matches(r, filter));
801
+ }
802
+ async sum(filter) {
803
+ const matched = this.records.filter((r) => this.matches(r, filter));
804
+ const total = matched.reduce((acc, r) => acc + r.cost, 0);
805
+ const currency = matched[0]?.currency ?? "USDC";
806
+ return { total, currency };
807
+ }
808
+ matches(record, filter) {
809
+ if (filter.domain && record.domain !== filter.domain) return false;
810
+ if (filter.agentId && record.agentId !== filter.agentId) return false;
811
+ if (filter.developerId && record.developerId !== filter.developerId) return false;
812
+ if (filter.from && record.timestamp < filter.from) return false;
813
+ if (filter.to && record.timestamp > filter.to) return false;
814
+ return true;
815
+ }
816
+ };
817
+
818
+ // src/metering/d1-store.ts
819
+ var CREATE_METER_RECORDS = `
820
+ CREATE TABLE IF NOT EXISTS meter_records (
821
+ id TEXT PRIMARY KEY,
822
+ timestamp INTEGER NOT NULL,
823
+ domain TEXT NOT NULL,
824
+ version TEXT NOT NULL,
825
+ endpoint TEXT NOT NULL,
826
+ agent_id TEXT NOT NULL,
827
+ developer_id TEXT,
828
+ cost REAL NOT NULL,
829
+ currency TEXT NOT NULL DEFAULT 'USDC'
830
+ )`;
831
+ var CREATE_METER_INDEX_DOMAIN = `
832
+ CREATE INDEX IF NOT EXISTS idx_meter_domain ON meter_records(domain, timestamp)`;
833
+ var CREATE_METER_INDEX_AGENT = `
834
+ CREATE INDEX IF NOT EXISTS idx_meter_agent ON meter_records(agent_id, timestamp)`;
835
+ var D1MeterStore = class {
836
+ constructor(db) {
837
+ this.db = db;
838
+ }
839
+ async initSchema() {
840
+ await this.db.exec(CREATE_METER_RECORDS);
841
+ await this.db.exec(CREATE_METER_INDEX_DOMAIN);
842
+ await this.db.exec(CREATE_METER_INDEX_AGENT);
843
+ }
844
+ async record(entry) {
845
+ await this.db.prepare(
846
+ `INSERT INTO meter_records (id, timestamp, domain, version, endpoint, agent_id, developer_id, cost, currency)
847
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
848
+ ).bind(
849
+ entry.id,
850
+ entry.timestamp,
851
+ entry.domain,
852
+ entry.version,
853
+ entry.endpoint,
854
+ entry.agentId,
855
+ entry.developerId ?? null,
856
+ entry.cost,
857
+ entry.currency
858
+ ).run();
859
+ }
860
+ async query(filter) {
861
+ const { sql, bindings } = this.buildQuery("SELECT *", filter);
862
+ const { results } = await this.db.prepare(sql).bind(...bindings).all();
863
+ return results.map(rowToRecord2);
864
+ }
865
+ async sum(filter) {
866
+ const { sql, bindings } = this.buildQuery("SELECT COALESCE(SUM(cost), 0) as total, COALESCE(MIN(currency), 'USDC') as currency", filter);
867
+ const row = await this.db.prepare(sql).bind(...bindings).first();
868
+ return { total: row?.total ?? 0, currency: row?.currency ?? "USDC" };
869
+ }
870
+ buildQuery(select, filter) {
871
+ const conditions = [];
872
+ const bindings = [];
873
+ if (filter.domain) {
874
+ conditions.push("domain = ?");
875
+ bindings.push(filter.domain);
876
+ }
877
+ if (filter.agentId) {
878
+ conditions.push("agent_id = ?");
879
+ bindings.push(filter.agentId);
880
+ }
881
+ if (filter.developerId) {
882
+ conditions.push("developer_id = ?");
883
+ bindings.push(filter.developerId);
884
+ }
885
+ if (filter.from) {
886
+ conditions.push("timestamp >= ?");
887
+ bindings.push(filter.from);
888
+ }
889
+ if (filter.to) {
890
+ conditions.push("timestamp <= ?");
891
+ bindings.push(filter.to);
892
+ }
893
+ let sql = `${select} FROM meter_records`;
894
+ if (conditions.length > 0) {
895
+ sql += ` WHERE ${conditions.join(" AND ")}`;
896
+ }
897
+ sql += " ORDER BY timestamp DESC";
898
+ return { sql, bindings };
899
+ }
900
+ };
901
+ function rowToRecord2(row) {
902
+ return {
903
+ id: row.id,
904
+ timestamp: row.timestamp,
905
+ domain: row.domain,
906
+ version: row.version,
907
+ endpoint: row.endpoint,
908
+ agentId: row.agent_id,
909
+ ...row.developer_id ? { developerId: row.developer_id } : {},
910
+ cost: row.cost,
911
+ currency: row.currency
912
+ };
913
+ }
914
+
915
+ // src/metering/pricing-guard.ts
916
+ function lookupPricing(record, method, path) {
917
+ for (const ep of record.endpoints) {
918
+ if (ep.method.toUpperCase() !== method.toUpperCase()) continue;
919
+ if (matchPath(ep.path, path)) {
920
+ return ep.pricing ?? null;
921
+ }
922
+ }
923
+ return null;
924
+ }
925
+ function checkAccess(record) {
926
+ if (record.status === "sunset") {
927
+ return { allowed: false, reason: "Service has been sunset" };
928
+ }
929
+ if (record.sunsetDate && record.sunsetDate < Date.now()) {
930
+ return { allowed: false, reason: "Service sunset date has passed" };
931
+ }
932
+ return { allowed: true };
933
+ }
934
+ async function meter(store, opts) {
935
+ const entry = {
936
+ id: `m_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
937
+ timestamp: Date.now(),
938
+ domain: opts.domain,
939
+ version: opts.version,
940
+ endpoint: opts.endpoint,
941
+ agentId: opts.agentId,
942
+ developerId: opts.developerId,
943
+ cost: opts.pricing.cost,
944
+ currency: opts.pricing.currency
945
+ };
946
+ await store.record(entry);
947
+ return entry;
948
+ }
949
+ function matchPath(pattern, actual) {
950
+ const patternParts = pattern.split("/").filter(Boolean);
951
+ const actualParts = actual.split("/").filter(Boolean);
952
+ if (patternParts.length !== actualParts.length) return false;
953
+ for (let i = 0; i < patternParts.length; i++) {
954
+ if (patternParts[i].startsWith(":")) continue;
955
+ if (patternParts[i] !== actualParts[i]) return false;
956
+ }
957
+ return true;
958
+ }
959
+
960
+ // src/credential/memory-vault.ts
961
+ var MemoryCredentialVault = class {
962
+ // key = "pool:domain" or "byok:domain:developerId"
963
+ credentials = /* @__PURE__ */ new Map();
964
+ poolKey(domain) {
965
+ return `pool:${domain}`;
966
+ }
967
+ byokKey(domain, developerId) {
968
+ return `byok:${domain}:${developerId}`;
969
+ }
970
+ async get(domain, developerId) {
971
+ if (developerId) {
972
+ const byok = this.credentials.get(this.byokKey(domain, developerId));
973
+ if (byok) return byok;
974
+ }
975
+ return this.credentials.get(this.poolKey(domain)) ?? null;
976
+ }
977
+ async putPool(domain, auth) {
978
+ this.credentials.set(this.poolKey(domain), { domain, auth, scope: "pool" });
979
+ }
980
+ async putByok(domain, developerId, auth) {
981
+ this.credentials.set(this.byokKey(domain, developerId), {
982
+ domain,
983
+ auth,
984
+ scope: "byok",
985
+ developerId
986
+ });
987
+ }
988
+ async delete(domain, developerId) {
989
+ if (developerId) {
990
+ this.credentials.delete(this.byokKey(domain, developerId));
991
+ } else {
992
+ this.credentials.delete(this.poolKey(domain));
993
+ }
994
+ }
995
+ async listDomains() {
996
+ const domains = /* @__PURE__ */ new Set();
997
+ for (const cred of this.credentials.values()) {
998
+ domains.add(cred.domain);
999
+ }
1000
+ return Array.from(domains);
1001
+ }
1002
+ };
1003
+
1004
+ // src/credential/d1-vault.ts
1005
+ var CREATE_CREDENTIALS = `
1006
+ CREATE TABLE IF NOT EXISTS credentials (
1007
+ domain TEXT NOT NULL,
1008
+ scope TEXT NOT NULL DEFAULT 'pool',
1009
+ developer_id TEXT NOT NULL DEFAULT '',
1010
+ auth_encrypted TEXT NOT NULL,
1011
+ created_at INTEGER NOT NULL,
1012
+ updated_at INTEGER NOT NULL,
1013
+ PRIMARY KEY (domain, scope, developer_id)
1014
+ )`;
1015
+ async function encrypt(auth, key) {
1016
+ const iv = crypto.getRandomValues(new Uint8Array(12));
1017
+ const plaintext = new TextEncoder().encode(JSON.stringify(auth));
1018
+ const ciphertext = await crypto.subtle.encrypt(
1019
+ { name: "AES-GCM", iv },
1020
+ key,
1021
+ plaintext
1022
+ );
1023
+ const combined = new Uint8Array(iv.length + ciphertext.byteLength);
1024
+ combined.set(iv);
1025
+ combined.set(new Uint8Array(ciphertext), iv.length);
1026
+ return btoa(String.fromCharCode(...combined));
1027
+ }
1028
+ async function decrypt(encoded, key) {
1029
+ const bytes = Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
1030
+ const iv = bytes.slice(0, 12);
1031
+ const ciphertext = bytes.slice(12);
1032
+ try {
1033
+ const plaintext = await crypto.subtle.decrypt(
1034
+ { name: "AES-GCM", iv },
1035
+ key,
1036
+ ciphertext
1037
+ );
1038
+ return JSON.parse(new TextDecoder().decode(plaintext));
1039
+ } catch {
1040
+ return JSON.parse(atob(encoded));
1041
+ }
1042
+ }
1043
+ var D1CredentialVault = class {
1044
+ constructor(db, encryptionKey) {
1045
+ this.db = db;
1046
+ this.encryptionKey = encryptionKey;
1047
+ }
1048
+ async initSchema() {
1049
+ await this.db.exec(CREATE_CREDENTIALS);
1050
+ }
1051
+ async get(domain, developerId) {
1052
+ if (developerId) {
1053
+ const byok = await this.db.prepare("SELECT * FROM credentials WHERE domain = ? AND scope = 'byok' AND developer_id = ?").bind(domain, developerId).first();
1054
+ if (byok) return await this.rowToCredential(byok);
1055
+ }
1056
+ const pool = await this.db.prepare("SELECT * FROM credentials WHERE domain = ? AND scope = 'pool' AND developer_id = ''").bind(domain).first();
1057
+ return pool ? await this.rowToCredential(pool) : null;
1058
+ }
1059
+ async putPool(domain, auth) {
1060
+ const now = Date.now();
1061
+ await this.db.prepare(
1062
+ `INSERT OR REPLACE INTO credentials (domain, scope, developer_id, auth_encrypted, created_at, updated_at)
1063
+ VALUES (?, 'pool', '', ?, ?, ?)`
1064
+ ).bind(domain, await encrypt(auth, this.encryptionKey), now, now).run();
1065
+ }
1066
+ async putByok(domain, developerId, auth) {
1067
+ const now = Date.now();
1068
+ await this.db.prepare(
1069
+ `INSERT OR REPLACE INTO credentials (domain, scope, developer_id, auth_encrypted, created_at, updated_at)
1070
+ VALUES (?, 'byok', ?, ?, ?, ?)`
1071
+ ).bind(domain, developerId, await encrypt(auth, this.encryptionKey), now, now).run();
1072
+ }
1073
+ async delete(domain, developerId) {
1074
+ if (developerId) {
1075
+ await this.db.prepare("DELETE FROM credentials WHERE domain = ? AND scope = 'byok' AND developer_id = ?").bind(domain, developerId).run();
1076
+ } else {
1077
+ await this.db.prepare("DELETE FROM credentials WHERE domain = ? AND scope = 'pool' AND developer_id = ''").bind(domain).run();
1078
+ }
1079
+ }
1080
+ async listDomains() {
1081
+ const { results } = await this.db.prepare("SELECT DISTINCT domain FROM credentials").all();
1082
+ return results.map((r) => r.domain);
1083
+ }
1084
+ async rowToCredential(row) {
1085
+ return {
1086
+ domain: row.domain,
1087
+ auth: await decrypt(row.auth_encrypted, this.encryptionKey),
1088
+ scope: row.scope,
1089
+ ...row.developer_id ? { developerId: row.developer_id } : {}
1090
+ };
1091
+ }
1092
+ };
1093
+
1094
+ // src/http/routes/credentials.ts
1095
+ var import_hono = require("hono");
1096
+ function credentialRoutes(options) {
1097
+ const { vault } = options;
1098
+ const app = new import_hono.Hono();
1099
+ app.put("/:domain", async (c) => {
1100
+ const domain = c.req.param("domain");
1101
+ const body = await c.req.json();
1102
+ if (!body.auth?.type) {
1103
+ return c.json({ error: "Missing auth.type" }, 400);
1104
+ }
1105
+ await vault.putPool(domain, body.auth);
1106
+ return c.json({ ok: true, domain });
1107
+ });
1108
+ app.get("/", async (c) => {
1109
+ const domains = await vault.listDomains();
1110
+ return c.json({ domains });
1111
+ });
1112
+ app.delete("/:domain", async (c) => {
1113
+ const domain = c.req.param("domain");
1114
+ await vault.delete(domain);
1115
+ return c.json({ ok: true, domain });
1116
+ });
1117
+ return app;
1118
+ }
1119
+
1120
+ // src/http/lib/dns.ts
1121
+ async function queryDnsTxt(domain) {
1122
+ const url = new URL("https://cloudflare-dns.com/dns-query");
1123
+ url.searchParams.set("name", domain);
1124
+ url.searchParams.set("type", "TXT");
1125
+ const res = await fetch(url.toString(), {
1126
+ headers: { Accept: "application/dns-json" }
1127
+ });
1128
+ if (!res.ok) {
1129
+ throw new Error(`DNS query failed: ${res.status}`);
1130
+ }
1131
+ const data = await res.json();
1132
+ return (data.Answer ?? []).filter((a) => a.type === 16).map((a) => a.data.replace(/^"|"$/g, ""));
1133
+ }
1134
+
1135
+ // src/registry/context7.ts
1136
+ var Context7Client = class {
1137
+ apiKey;
1138
+ baseUrl;
1139
+ fetchFn;
1140
+ constructor(options) {
1141
+ this.apiKey = options?.apiKey;
1142
+ this.baseUrl = options?.baseUrl ?? "https://context7.com/api/v2";
1143
+ this.fetchFn = options?.fetchFn ?? globalThis.fetch.bind(globalThis);
1144
+ }
1145
+ /** Search for a library by name. Returns matching library entries. */
1146
+ async searchLibraries(libraryName, query) {
1147
+ const params = new URLSearchParams({ libraryName });
1148
+ if (query) params.set("query", query);
1149
+ const resp = await this.fetchFn(`${this.baseUrl}/libs/search?${params}`, {
1150
+ headers: this.headers()
1151
+ });
1152
+ if (!resp.ok) throw new Error(`Context7 search failed: ${resp.status}`);
1153
+ return resp.json();
1154
+ }
1155
+ /** Query documentation for a specific library. Returns documentation text. */
1156
+ async queryDocs(libraryId, query) {
1157
+ const params = new URLSearchParams({ libraryId, query, type: "txt" });
1158
+ const resp = await this.fetchFn(`${this.baseUrl}/context?${params}`, {
1159
+ headers: this.headers()
1160
+ });
1161
+ if (!resp.ok) throw new Error(`Context7 query failed: ${resp.status}`);
1162
+ return resp.text();
1163
+ }
1164
+ headers() {
1165
+ const h = {};
1166
+ if (this.apiKey) h["Authorization"] = `Bearer ${this.apiKey}`;
1167
+ return h;
1168
+ }
1169
+ };
1170
+
1171
+ // src/registry/context7-backend.ts
1172
+ var Context7Backend = class {
1173
+ client;
1174
+ constructor(options) {
1175
+ this.client = new Context7Client(options);
1176
+ }
1177
+ async list(path) {
1178
+ const cleaned = path.replace(/^\/+/, "").replace(/\/+$/, "");
1179
+ if (!cleaned) {
1180
+ return [
1181
+ 'grep "<\u5173\u952E\u8BCD>" /context7/ \u2014 \u641C\u7D22\u5E93',
1182
+ 'grep "<\u95EE\u9898>" /context7/{id} \u2014 \u67E5\u8BE2\u6587\u6863',
1183
+ "cat /context7/{owner}/{repo} \u2014 \u5E93\u6982\u89C8"
1184
+ ];
1185
+ }
1186
+ return ['grep "<\u95EE\u9898>" /context7/' + cleaned + " \u2014 \u67E5\u8BE2\u6B64\u5E93\u6587\u6863"];
1187
+ }
1188
+ async read(path) {
1189
+ const libraryId = parseLibraryId(path);
1190
+ if (!libraryId) {
1191
+ return { usage: 'grep "<\u5173\u952E\u8BCD>" /context7/ \u2014 \u641C\u7D22\u5E93' };
1192
+ }
1193
+ const name = libraryId.split("/").pop() ?? libraryId;
1194
+ const docs = await this.client.queryDocs(libraryId, `${name} overview getting started`);
1195
+ return { libraryId, docs };
1196
+ }
1197
+ async write(_path, _data) {
1198
+ throw new Error("context7 is read-only");
1199
+ }
1200
+ async remove(_path) {
1201
+ throw new Error("context7 is read-only");
1202
+ }
1203
+ async search(path, pattern) {
1204
+ const cleaned = path.replace(/^\/+/, "").replace(/\/+$/, "");
1205
+ if (!cleaned) {
1206
+ const results = await this.client.searchLibraries(pattern);
1207
+ return results.map(formatSearchResult);
1208
+ }
1209
+ const libraryId = parseLibraryId(path);
1210
+ if (!libraryId) return [];
1211
+ const docs = await this.client.queryDocs(libraryId, pattern);
1212
+ if (!docs) return [];
1213
+ return [{ libraryId, query: pattern, docs }];
1214
+ }
1215
+ };
1216
+ function parseLibraryId(path) {
1217
+ const cleaned = path.replace(/^\/+/, "").replace(/\/+$/, "");
1218
+ if (!cleaned) return null;
1219
+ const parts = cleaned.split("/");
1220
+ if (parts.length < 2) return null;
1221
+ return "/" + parts.slice(0, 2).join("/");
1222
+ }
1223
+ function formatSearchResult(r) {
1224
+ return {
1225
+ id: r.id,
1226
+ name: r.name,
1227
+ description: r.description ?? "",
1228
+ snippets: r.totalSnippets ?? 0
1229
+ };
1230
+ }
1231
+
1232
+ // src/onboard/pipeline.ts
1233
+ var import_agent_fs2 = require("@nkmc/agent-fs");
1234
+
1235
+ // src/registry/openapi-compiler.ts
1236
+ var import_yaml2 = __toESM(require("yaml"), 1);
1237
+ function extractBasePath(spec) {
1238
+ const servers = spec.servers;
1239
+ if (!Array.isArray(servers) || servers.length === 0) return "";
1240
+ const serverUrl = servers[0]?.url;
1241
+ if (!serverUrl || typeof serverUrl !== "string") return "";
1242
+ try {
1243
+ if (serverUrl.startsWith("/")) {
1244
+ return serverUrl.replace(/\/+$/, "");
1245
+ }
1246
+ const parsed = new URL(serverUrl);
1247
+ const pathname = parsed.pathname.replace(/\/+$/, "");
1248
+ return pathname || "";
1249
+ } catch {
1250
+ return "";
1251
+ }
1252
+ }
1253
+ function resolveRef(spec, ref) {
1254
+ if (!ref.startsWith("#/")) return void 0;
1255
+ const parts = ref.slice(2).split("/");
1256
+ let current = spec;
1257
+ for (const part of parts) {
1258
+ if (current == null || typeof current !== "object") return void 0;
1259
+ current = current[part];
1260
+ }
1261
+ return current;
1262
+ }
1263
+ function resolveSchema(spec, schema) {
1264
+ if (!schema) return void 0;
1265
+ if (schema.$ref) return resolveRef(spec, schema.$ref);
1266
+ return schema;
1267
+ }
1268
+ function extractProperties(spec, schema) {
1269
+ const resolved = resolveSchema(spec, schema);
1270
+ if (!resolved || resolved.type !== "object" || !resolved.properties) return [];
1271
+ const requiredSet = new Set(resolved.required ?? []);
1272
+ const props = [];
1273
+ for (const [name, prop] of Object.entries(resolved.properties)) {
1274
+ const p = resolveSchema(spec, prop) ?? prop;
1275
+ props.push({
1276
+ name,
1277
+ type: p.type ?? (p.enum ? "enum" : "unknown"),
1278
+ required: requiredSet.has(name),
1279
+ ...p.description ? { description: p.description } : {}
1280
+ });
1281
+ }
1282
+ return props;
1283
+ }
1284
+ function extractParams(spec, operation) {
1285
+ const params = operation.parameters;
1286
+ if (!Array.isArray(params) || params.length === 0) return void 0;
1287
+ const result = [];
1288
+ for (const raw of params) {
1289
+ const p = resolveSchema(spec, raw) ?? raw;
1290
+ if (!p.name || !p.in) continue;
1291
+ if (!["path", "query", "header"].includes(p.in)) continue;
1292
+ const schema = resolveSchema(spec, p.schema) ?? p.schema;
1293
+ result.push({
1294
+ name: p.name,
1295
+ in: p.in,
1296
+ required: !!p.required,
1297
+ type: schema?.type ?? "string",
1298
+ ...p.description ? { description: p.description } : {}
1299
+ });
1300
+ }
1301
+ return result.length > 0 ? result : void 0;
1302
+ }
1303
+ function extractRequestBody(spec, operation) {
1304
+ const body = resolveSchema(spec, operation.requestBody);
1305
+ if (!body?.content) return void 0;
1306
+ const jsonContent = body.content["application/json"];
1307
+ if (!jsonContent?.schema) return void 0;
1308
+ const properties = extractProperties(spec, jsonContent.schema);
1309
+ if (properties.length === 0) return void 0;
1310
+ return {
1311
+ contentType: "application/json",
1312
+ required: !!body.required,
1313
+ properties
1314
+ };
1315
+ }
1316
+ function extractResponses(spec, operation) {
1317
+ const responses = operation.responses;
1318
+ if (!responses || typeof responses !== "object") return void 0;
1319
+ const result = [];
1320
+ for (const [code, raw] of Object.entries(responses)) {
1321
+ const status = parseInt(code, 10);
1322
+ if (isNaN(status) || status < 200 || status >= 300) continue;
1323
+ const resp = resolveSchema(spec, raw) ?? raw;
1324
+ const jsonContent = resp?.content?.["application/json"];
1325
+ const properties = jsonContent?.schema ? extractProperties(spec, jsonContent.schema) : void 0;
1326
+ result.push({
1327
+ status,
1328
+ description: resp?.description ?? "",
1329
+ ...properties && properties.length > 0 ? { properties } : {}
1330
+ });
1331
+ }
1332
+ return result.length > 0 ? result : void 0;
1333
+ }
1334
+ function compileOpenApiSpec(spec, options) {
1335
+ const info = spec.info ?? {};
1336
+ const name = info.title ?? options.domain;
1337
+ const description = info.description ?? "";
1338
+ const version = options.version ?? info.version ?? "1.0";
1339
+ const basePath = extractBasePath(spec);
1340
+ const endpoints = [];
1341
+ const resources = [];
1342
+ const resourcePaths = /* @__PURE__ */ new Map();
1343
+ const paths = spec.paths ?? {};
1344
+ for (const [path, methods] of Object.entries(paths)) {
1345
+ if (typeof methods !== "object" || methods === null) continue;
1346
+ for (const [method, op] of Object.entries(methods)) {
1347
+ if (!["get", "post", "put", "patch", "delete"].includes(method)) continue;
1348
+ const operation = op;
1349
+ const parameters = extractParams(spec, operation);
1350
+ const requestBody = extractRequestBody(spec, operation);
1351
+ const responses = extractResponses(spec, operation);
1352
+ endpoints.push({
1353
+ method: method.toUpperCase(),
1354
+ path,
1355
+ description: operation.summary ?? operation.operationId ?? `${method.toUpperCase()} ${path}`,
1356
+ ...parameters ? { parameters } : {},
1357
+ ...requestBody ? { requestBody } : {},
1358
+ ...responses ? { responses } : {}
1359
+ });
1360
+ }
1361
+ const segments = path.split("/").filter(Boolean);
1362
+ if (segments.length >= 1) {
1363
+ const resourceName = segments[0];
1364
+ if (!resourcePaths.has(resourceName) && !resourceName.startsWith("{")) {
1365
+ resourcePaths.set(resourceName, {
1366
+ name: resourceName,
1367
+ apiPath: `/${resourceName}`
1368
+ });
1369
+ }
1370
+ }
1371
+ }
1372
+ for (const r of resourcePaths.values()) {
1373
+ resources.push(r);
1374
+ }
1375
+ const skillMd = generateSkillMd(name, version, description, endpoints, resources);
1376
+ const now = Date.now();
1377
+ const record = {
1378
+ domain: options.domain,
1379
+ name,
1380
+ description,
1381
+ version,
1382
+ roles: ["agent"],
1383
+ skillMd,
1384
+ endpoints,
1385
+ isFirstParty: options.isFirstParty ?? false,
1386
+ createdAt: now,
1387
+ updatedAt: now,
1388
+ status: "active",
1389
+ isDefault: true,
1390
+ source: { type: "openapi", ...basePath ? { basePath } : {} }
1391
+ };
1392
+ return { record, resources, skillMd };
1393
+ }
1394
+ async function fetchAndCompile(specUrl, options, fetchFn = globalThis.fetch.bind(globalThis)) {
1395
+ const resp = await fetchFn(specUrl);
1396
+ if (!resp.ok) throw new Error(`Failed to fetch spec: ${resp.status} ${resp.statusText}`);
1397
+ const text = await resp.text();
1398
+ const spec = parseSpec(specUrl, resp.headers.get("content-type") ?? "", text);
1399
+ const result = compileOpenApiSpec(spec, options);
1400
+ const basePath = result.record.source?.basePath;
1401
+ result.record.source = { type: "openapi", url: specUrl, ...basePath ? { basePath } : {} };
1402
+ return result;
1403
+ }
1404
+ function parseSpec(url, contentType, text) {
1405
+ const isJson = contentType.includes("json") || url.endsWith(".json") || text.trimStart().startsWith("{");
1406
+ if (isJson) return JSON.parse(text);
1407
+ return import_yaml2.default.parse(text);
1408
+ }
1409
+ function propsTable(props) {
1410
+ let t = "| name | type | required |\n|------|------|----------|\n";
1411
+ for (const p of props) {
1412
+ t += `| ${p.name} | ${p.type} | ${p.required ? "*" : ""} |
1413
+ `;
1414
+ }
1415
+ return t;
1416
+ }
1417
+ function generateSkillMd(name, version, description, endpoints, resources) {
1418
+ let md = `---
1419
+ name: "${name}"
1420
+ gateway: nkmc
1421
+ version: "${version}"
1422
+ roles: [agent]
1423
+ ---
1424
+
1425
+ `;
1426
+ md += `# ${name}
1427
+
1428
+ ${description}
1429
+
1430
+ `;
1431
+ if (resources.length > 0) {
1432
+ md += `## Schema
1433
+
1434
+ `;
1435
+ for (const r of resources) {
1436
+ md += `### ${r.name} (public)
1437
+
1438
+ `;
1439
+ }
1440
+ }
1441
+ if (endpoints.length > 0) {
1442
+ md += `## API
1443
+
1444
+ `;
1445
+ for (const ep of endpoints) {
1446
+ md += `### ${ep.description}
1447
+
1448
+ `;
1449
+ md += `\`${ep.method} ${ep.path}\`
1450
+
1451
+ `;
1452
+ if (ep.parameters && ep.parameters.length > 0) {
1453
+ md += "**Parameters:**\n\n";
1454
+ md += "| name | in | type | required |\n|------|-----|------|----------|\n";
1455
+ for (const p of ep.parameters) {
1456
+ md += `| ${p.name} | ${p.in} | ${p.type} | ${p.required ? "*" : ""} |
1457
+ `;
1458
+ }
1459
+ md += "\n";
1460
+ }
1461
+ if (ep.requestBody) {
1462
+ const req = ep.requestBody;
1463
+ md += `**Body** (${req.contentType}${req.required ? ", required" : ""}):
1464
+
1465
+ `;
1466
+ md += propsTable(req.properties);
1467
+ md += "\n";
1468
+ }
1469
+ if (ep.responses && ep.responses.length > 0) {
1470
+ for (const r of ep.responses) {
1471
+ md += `**Response ${r.status}**${r.description ? `: ${r.description}` : ""}
1472
+
1473
+ `;
1474
+ if (r.properties && r.properties.length > 0) {
1475
+ md += propsTable(r.properties);
1476
+ md += "\n";
1477
+ }
1478
+ }
1479
+ }
1480
+ }
1481
+ }
1482
+ return md;
1483
+ }
1484
+
1485
+ // src/registry/rpc-compiler.ts
1486
+ function compileRpcDef(domain, rpcDef, options) {
1487
+ const convention = rpcDef.convention ?? "raw";
1488
+ const endpoints = rpcDef.methods.map((m) => ({
1489
+ method: "RPC",
1490
+ path: m.rpcMethod,
1491
+ description: m.description
1492
+ }));
1493
+ const resourceMap = /* @__PURE__ */ new Map();
1494
+ for (const m of rpcDef.methods) {
1495
+ const resName = m.resource ?? inferResource(m.rpcMethod);
1496
+ if (!resourceMap.has(resName)) {
1497
+ resourceMap.set(resName, { name: resName, methods: {} });
1498
+ }
1499
+ const entry = resourceMap.get(resName);
1500
+ if (m.fsOp) {
1501
+ entry.methods[m.fsOp] = m.rpcMethod;
1502
+ }
1503
+ }
1504
+ const rpcMeta = {
1505
+ rpcUrl: rpcDef.url,
1506
+ convention,
1507
+ resources: Array.from(resourceMap.values())
1508
+ };
1509
+ const skillMd = generateSkillMd2(domain, rpcDef, endpoints);
1510
+ const now = Date.now();
1511
+ const record = {
1512
+ domain,
1513
+ name: domain,
1514
+ description: `JSON-RPC service at ${domain}`,
1515
+ version: options?.version ?? "1.0",
1516
+ roles: ["agent"],
1517
+ skillMd,
1518
+ endpoints,
1519
+ isFirstParty: options?.isFirstParty ?? false,
1520
+ createdAt: now,
1521
+ updatedAt: now,
1522
+ status: "active",
1523
+ isDefault: true,
1524
+ source: {
1525
+ type: "jsonrpc",
1526
+ url: rpcDef.url,
1527
+ rpc: rpcMeta
1528
+ }
1529
+ };
1530
+ return { record, skillMd };
1531
+ }
1532
+ function inferResource(rpcMethod) {
1533
+ const underscoreIdx = rpcMethod.indexOf("_");
1534
+ if (underscoreIdx < 0) return rpcMethod;
1535
+ const action = rpcMethod.slice(underscoreIdx + 1);
1536
+ const verbPrefixes = ["get", "send", "subscribe", "unsubscribe", "new", "call"];
1537
+ let noun = action;
1538
+ for (const prefix of verbPrefixes) {
1539
+ if (action.startsWith(prefix) && action.length > prefix.length) {
1540
+ noun = action.slice(prefix.length);
1541
+ break;
1542
+ }
1543
+ }
1544
+ const camelMatch = noun.match(/^([A-Z][a-z]+)/);
1545
+ if (camelMatch) {
1546
+ noun = camelMatch[1];
1547
+ }
1548
+ const lower = noun.toLowerCase();
1549
+ return lower.endsWith("s") ? lower : lower + "s";
1550
+ }
1551
+ function generateSkillMd2(domain, rpcDef, endpoints) {
1552
+ const lines = [
1553
+ "---",
1554
+ `name: "${domain}"`,
1555
+ `version: "1.0"`,
1556
+ `roles: [agent]`,
1557
+ "---",
1558
+ "",
1559
+ `# ${domain}`,
1560
+ "",
1561
+ `JSON-RPC service at ${rpcDef.url}`,
1562
+ "",
1563
+ `Convention: ${rpcDef.convention ?? "raw"}`,
1564
+ "",
1565
+ "## RPC Methods",
1566
+ "",
1567
+ "| method | description |",
1568
+ "|--------|-------------|"
1569
+ ];
1570
+ for (const ep of endpoints) {
1571
+ lines.push(`| ${ep.path} | ${ep.description} |`);
1572
+ }
1573
+ return lines.join("\n");
1574
+ }
1575
+
1576
+ // src/onboard/pipeline.ts
1577
+ var OnboardPipeline = class {
1578
+ store;
1579
+ vault;
1580
+ smokeTest;
1581
+ concurrency;
1582
+ fetchFn;
1583
+ onProgress;
1584
+ constructor(options) {
1585
+ this.store = options.store;
1586
+ this.vault = options.vault;
1587
+ this.smokeTest = options.smokeTest !== false;
1588
+ this.concurrency = options.concurrency ?? 5;
1589
+ this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
1590
+ this.onProgress = options.onProgress;
1591
+ }
1592
+ /** Onboard a single service */
1593
+ async onboardOne(entry) {
1594
+ const start = Date.now();
1595
+ const base = {
1596
+ domain: entry.domain,
1597
+ source: "none",
1598
+ endpoints: 0,
1599
+ resources: 0,
1600
+ hasCredentials: false
1601
+ };
1602
+ if (entry.disabled) {
1603
+ return { ...base, status: "skipped", durationMs: Date.now() - start };
1604
+ }
1605
+ try {
1606
+ if (entry.specUrl) {
1607
+ const result = await fetchAndCompile(entry.specUrl, { domain: entry.domain }, this.fetchFn);
1608
+ await this.store.put(entry.domain, result.record);
1609
+ base.source = "openapi";
1610
+ base.endpoints = result.record.endpoints.length;
1611
+ base.resources = result.resources.length;
1612
+ } else if (entry.skillMdUrl) {
1613
+ const resp = await this.fetchFn(entry.skillMdUrl);
1614
+ if (!resp.ok) throw new Error(`Failed to fetch skill.md: ${resp.status}`);
1615
+ const md = await resp.text();
1616
+ const record = parseSkillMd(entry.domain, md);
1617
+ await this.store.put(entry.domain, record);
1618
+ base.source = "wellknown";
1619
+ base.endpoints = record.endpoints.length;
1620
+ } else if (entry.skillMd) {
1621
+ const record = parseSkillMd(entry.domain, entry.skillMd);
1622
+ await this.store.put(entry.domain, record);
1623
+ base.source = "skillmd";
1624
+ base.endpoints = record.endpoints.length;
1625
+ } else if (entry.rpcDef) {
1626
+ const { record } = compileRpcDef(entry.domain, entry.rpcDef);
1627
+ await this.store.put(entry.domain, record);
1628
+ base.source = "jsonrpc";
1629
+ base.endpoints = record.endpoints.length;
1630
+ base.resources = record.source?.rpc?.resources.length ?? 0;
1631
+ } else {
1632
+ return { ...base, status: "skipped", error: "No spec, skillMdUrl, or skillMd provided", durationMs: Date.now() - start };
1633
+ }
1634
+ if (entry.auth && this.vault) {
1635
+ const auth = resolveAuth(entry.auth);
1636
+ await this.vault.putPool(entry.domain, auth);
1637
+ base.hasCredentials = true;
1638
+ }
1639
+ if (this.smokeTest) {
1640
+ base.smokeTest = await this.runSmokeTest(entry.domain, base.hasCredentials);
1641
+ }
1642
+ return { ...base, status: "ok", durationMs: Date.now() - start };
1643
+ } catch (err) {
1644
+ return {
1645
+ ...base,
1646
+ status: "failed",
1647
+ error: err instanceof Error ? err.message : String(err),
1648
+ durationMs: Date.now() - start
1649
+ };
1650
+ }
1651
+ }
1652
+ /** Onboard many services with controlled concurrency */
1653
+ async onboardMany(entries) {
1654
+ const start = Date.now();
1655
+ const results = [];
1656
+ let index = 0;
1657
+ for (let i = 0; i < entries.length; i += this.concurrency) {
1658
+ const batch = entries.slice(i, i + this.concurrency);
1659
+ const batchResults = await Promise.all(
1660
+ batch.map(async (entry) => {
1661
+ const result = await this.onboardOne(entry);
1662
+ const idx = index++;
1663
+ this.onProgress?.(result, idx, entries.length);
1664
+ return result;
1665
+ })
1666
+ );
1667
+ results.push(...batchResults);
1668
+ }
1669
+ return {
1670
+ total: results.length,
1671
+ ok: results.filter((r) => r.status === "ok").length,
1672
+ failed: results.filter((r) => r.status === "failed").length,
1673
+ skipped: results.filter((r) => r.status === "skipped").length,
1674
+ results,
1675
+ durationMs: Date.now() - start
1676
+ };
1677
+ }
1678
+ async runSmokeTest(domain, hasCredentials) {
1679
+ const resolverOpts = this.vault ? { store: this.store, vault: this.vault, wrapVirtualFiles: false } : { store: this.store, wrapVirtualFiles: false };
1680
+ const { onMiss, listDomains } = createRegistryResolver(resolverOpts);
1681
+ const fs = new import_agent_fs2.AgentFs({ mounts: [], onMiss, listDomains });
1682
+ const test = { ls: false, cat: false };
1683
+ const lsResult = await fs.execute(`ls /${domain}/`);
1684
+ test.ls = lsResult.ok === true;
1685
+ if (test.ls && lsResult.ok) {
1686
+ const entries = lsResult.data;
1687
+ const resource = entries.find((e) => e.endsWith("/") && !e.startsWith("_"));
1688
+ if (resource) {
1689
+ const catResult = await fs.execute(`ls /${domain}/${resource}`);
1690
+ test.cat = catResult.ok === true;
1691
+ test.catEndpoint = `ls /${domain}/${resource}`;
1692
+ } else if (entries.includes("_api/")) {
1693
+ const apiResult = await fs.execute(`ls /${domain}/_api/`);
1694
+ test.cat = apiResult.ok === true;
1695
+ test.catEndpoint = `ls /${domain}/_api/`;
1696
+ }
1697
+ }
1698
+ return test;
1699
+ }
1700
+ };
1701
+ function resolveAuth(auth) {
1702
+ const resolve = (val) => {
1703
+ if (!val) return void 0;
1704
+ const match = val.match(/^\$\{(\w+)\}$/);
1705
+ if (match) {
1706
+ const envVal = process.env[match[1]];
1707
+ if (!envVal) throw new Error(`Environment variable ${match[1]} is not set`);
1708
+ return envVal;
1709
+ }
1710
+ return val;
1711
+ };
1712
+ if (auth.type === "bearer") {
1713
+ const token = resolve(auth.token);
1714
+ if (!token) throw new Error("Bearer auth requires token");
1715
+ return { type: "bearer", token, ...auth.prefix ? { prefix: auth.prefix } : {} };
1716
+ }
1717
+ if (auth.type === "api-key") {
1718
+ const header = resolve(auth.header);
1719
+ const key = resolve(auth.key);
1720
+ if (!header || !key) throw new Error("API key auth requires header and key");
1721
+ return { type: "api-key", header, key };
1722
+ }
1723
+ if (auth.type === "basic") {
1724
+ const username = resolve(auth.username);
1725
+ const password = resolve(auth.password);
1726
+ if (!username || !password) throw new Error("Basic auth requires username and password");
1727
+ return { type: "basic", username, password };
1728
+ }
1729
+ if (auth.type === "oauth2") {
1730
+ const tokenUrl = resolve(auth.tokenUrl);
1731
+ const clientId = resolve(auth.clientId);
1732
+ const clientSecret = resolve(auth.clientSecret);
1733
+ if (!tokenUrl || !clientId || !clientSecret) throw new Error("OAuth2 auth requires tokenUrl, clientId, and clientSecret");
1734
+ return { type: "oauth2", tokenUrl, clientId, clientSecret, ...auth.scope ? { scope: auth.scope } : {} };
1735
+ }
1736
+ throw new Error(`Unknown auth type: ${auth.type}`);
1737
+ }
1738
+
1739
+ // src/onboard/apis-guru.ts
1740
+ var APIS_GURU_LIST = "https://api.apis.guru/v2/list.json";
1741
+ async function discoverFromApisGuru(options) {
1742
+ const fetchFn = options?.fetchFn ?? globalThis.fetch.bind(globalThis);
1743
+ const limit = options?.limit ?? 100;
1744
+ const filter = options?.filter?.toLowerCase();
1745
+ const resp = await fetchFn(APIS_GURU_LIST);
1746
+ if (!resp.ok) throw new Error(`apis.guru fetch failed: ${resp.status}`);
1747
+ const catalog = await resp.json();
1748
+ const entries = [];
1749
+ for (const [key, api] of Object.entries(catalog)) {
1750
+ if (entries.length >= limit) break;
1751
+ const version = api.versions[api.preferred];
1752
+ if (!version?.swaggerUrl) continue;
1753
+ const title = version.info?.title ?? key;
1754
+ const desc = version.info?.description ?? "";
1755
+ if (filter) {
1756
+ const text = `${key} ${title} ${desc}`.toLowerCase();
1757
+ if (!text.includes(filter)) continue;
1758
+ }
1759
+ const domain = key.split(":")[0];
1760
+ entries.push({
1761
+ domain,
1762
+ specUrl: version.swaggerUrl,
1763
+ tags: ["apis-guru", "public"]
1764
+ });
1765
+ }
1766
+ return entries;
1767
+ }
1768
+
1769
+ // src/onboard/manifest.ts
1770
+ var FREE_APIS = [
1771
+ {
1772
+ domain: "petstore3.swagger.io",
1773
+ specUrl: "https://petstore3.swagger.io/api/v3/openapi.json",
1774
+ tags: ["demo", "free"]
1775
+ },
1776
+ {
1777
+ domain: "api.weather.gov",
1778
+ specUrl: "https://api.weather.gov/openapi.json",
1779
+ tags: ["weather", "government", "free"]
1780
+ },
1781
+ {
1782
+ domain: "en.wikipedia.org",
1783
+ specUrl: "https://en.wikipedia.org/api/rest_v1/?spec",
1784
+ tags: ["knowledge", "encyclopedia", "free"]
1785
+ }
1786
+ ];
1787
+ var FREEMIUM_APIS = [
1788
+ {
1789
+ domain: "api.github.com",
1790
+ specUrl: "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.2022-11-28.json",
1791
+ auth: { type: "bearer", token: "${GITHUB_TOKEN}" },
1792
+ tags: ["developer-tools", "vcs", "freemium"]
1793
+ },
1794
+ {
1795
+ domain: "huggingface.co",
1796
+ specUrl: "https://huggingface.co/.well-known/openapi.json",
1797
+ auth: { type: "bearer", token: "${HF_TOKEN}" },
1798
+ tags: ["ai", "ml", "models", "freemium"]
1799
+ }
1800
+ ];
1801
+ var DEVELOPER_TOOL_APIS = [
1802
+ {
1803
+ domain: "gitlab.com",
1804
+ specUrl: "https://gitlab.com/gitlab-org/gitlab/-/raw/master/doc/api/openapi/openapi.yaml",
1805
+ auth: { type: "bearer", token: "${GITLAB_TOKEN}" },
1806
+ tags: ["developer-tools", "vcs"]
1807
+ },
1808
+ {
1809
+ domain: "api.vercel.com",
1810
+ specUrl: "https://openapi.vercel.sh",
1811
+ auth: { type: "bearer", token: "${VERCEL_TOKEN}" },
1812
+ tags: ["developer-tools", "hosting"]
1813
+ },
1814
+ {
1815
+ domain: "sentry.io",
1816
+ specUrl: "https://raw.githubusercontent.com/getsentry/sentry-api-schema/main/openapi-derefed.json",
1817
+ auth: { type: "bearer", token: "${SENTRY_AUTH_TOKEN}" },
1818
+ tags: ["developer-tools", "monitoring"]
1819
+ },
1820
+ {
1821
+ domain: "api.pagerduty.com",
1822
+ specUrl: "https://raw.githubusercontent.com/PagerDuty/api-schema/main/reference/REST/openapiv3.json",
1823
+ auth: { type: "bearer", token: "${PAGERDUTY_TOKEN}" },
1824
+ tags: ["developer-tools", "incident-management"]
1825
+ }
1826
+ ];
1827
+ var AI_APIS = [
1828
+ {
1829
+ domain: "api.mistral.ai",
1830
+ specUrl: "https://raw.githubusercontent.com/mistralai/platform-docs-public/main/openapi.yaml",
1831
+ auth: { type: "bearer", token: "${MISTRAL_API_KEY}" },
1832
+ tags: ["ai", "llm"],
1833
+ disabled: true
1834
+ // upstream YAML spec has unescaped quotes in example data
1835
+ },
1836
+ {
1837
+ domain: "api.openai.com",
1838
+ specUrl: "https://raw.githubusercontent.com/openai/openai-openapi/manual_spec/openapi.yaml",
1839
+ auth: { type: "bearer", token: "${OPENAI_API_KEY}" },
1840
+ tags: ["ai", "llm"]
1841
+ },
1842
+ {
1843
+ domain: "openrouter.ai",
1844
+ specUrl: "https://openrouter.ai/openapi.json",
1845
+ auth: { type: "bearer", token: "${OPENROUTER_API_KEY}" },
1846
+ tags: ["ai", "llm", "gateway"]
1847
+ }
1848
+ ];
1849
+ var CLOUD_APIS = [
1850
+ {
1851
+ domain: "api.cloudflare.com",
1852
+ specUrl: "https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.yaml",
1853
+ auth: { type: "bearer", token: "${CLOUDFLARE_API_TOKEN}" },
1854
+ tags: ["cloud", "cdn", "dns"]
1855
+ },
1856
+ {
1857
+ domain: "api.digitalocean.com",
1858
+ specUrl: "https://raw.githubusercontent.com/digitalocean/openapi/main/specification/DigitalOcean-public.v2.yaml",
1859
+ auth: { type: "bearer", token: "${DIGITALOCEAN_TOKEN}" },
1860
+ tags: ["cloud", "infrastructure"]
1861
+ },
1862
+ {
1863
+ domain: "fly.io",
1864
+ specUrl: "https://docs.machines.dev/spec/openapi3.json",
1865
+ auth: { type: "bearer", token: "${FLY_API_TOKEN}" },
1866
+ tags: ["cloud", "deployment"]
1867
+ },
1868
+ {
1869
+ domain: "api.render.com",
1870
+ specUrl: "https://api-docs.render.com/v1.0/openapi/render-public-api-1.json",
1871
+ auth: { type: "bearer", token: "${RENDER_API_KEY}" },
1872
+ tags: ["cloud", "deployment"]
1873
+ }
1874
+ ];
1875
+ var PRODUCTIVITY_APIS = [
1876
+ {
1877
+ domain: "api.notion.com",
1878
+ specUrl: "https://raw.githubusercontent.com/makenotion/notion-mcp-server/main/scripts/notion-openapi.json",
1879
+ auth: { type: "bearer", token: "${NOTION_API_KEY}" },
1880
+ tags: ["productivity", "database"]
1881
+ },
1882
+ {
1883
+ domain: "app.asana.com",
1884
+ specUrl: "https://raw.githubusercontent.com/Asana/openapi/master/defs/asana_oas.yaml",
1885
+ auth: { type: "bearer", token: "${ASANA_ACCESS_TOKEN}" },
1886
+ tags: ["productivity", "project-management"]
1887
+ },
1888
+ {
1889
+ domain: "jira.atlassian.com",
1890
+ specUrl: "https://developer.atlassian.com/cloud/jira/platform/swagger-v3.v3.json",
1891
+ auth: { type: "bearer", token: "${ATLASSIAN_API_TOKEN}" },
1892
+ tags: ["productivity", "project-management"]
1893
+ },
1894
+ {
1895
+ domain: "api.spotify.com",
1896
+ specUrl: "https://raw.githubusercontent.com/sonallux/spotify-web-api/main/fixed-spotify-open-api.yml",
1897
+ auth: { type: "bearer", token: "${SPOTIFY_ACCESS_TOKEN}" },
1898
+ tags: ["media", "music"]
1899
+ },
1900
+ {
1901
+ domain: "api.getpostman.com",
1902
+ specUrl: "https://api.apis.guru/v2/specs/getpostman.com/1.20.0/openapi.json",
1903
+ auth: { type: "api-key", header: "X-Api-Key", key: "${POSTMAN_API_KEY}" },
1904
+ tags: ["developer-tools", "api-testing"]
1905
+ }
1906
+ ];
1907
+ var DEVOPS_APIS = [
1908
+ {
1909
+ domain: "circleci.com",
1910
+ specUrl: "https://circleci.com/api/v2/openapi.json",
1911
+ auth: { type: "bearer", token: "${CIRCLECI_TOKEN}" },
1912
+ tags: ["devops", "ci-cd"]
1913
+ },
1914
+ {
1915
+ domain: "api.datadoghq.com",
1916
+ specUrl: "https://raw.githubusercontent.com/DataDog/datadog-api-client-python/master/.generator/schemas/v2/openapi.yaml",
1917
+ auth: { type: "api-key", header: "DD-API-KEY", key: "${DATADOG_API_KEY}" },
1918
+ tags: ["devops", "monitoring"]
1919
+ }
1920
+ ];
1921
+ var DATABASE_APIS = [
1922
+ {
1923
+ domain: "api.supabase.com",
1924
+ specUrl: "https://raw.githubusercontent.com/supabase/supabase/master/apps/docs/spec/api_v1_openapi.json",
1925
+ auth: { type: "bearer", token: "${SUPABASE_ACCESS_TOKEN}" },
1926
+ tags: ["database", "baas"]
1927
+ },
1928
+ {
1929
+ domain: "api.turso.tech",
1930
+ specUrl: "https://raw.githubusercontent.com/tursodatabase/turso-docs/main/api-reference/openapi.json",
1931
+ auth: { type: "bearer", token: "${TURSO_API_TOKEN}" },
1932
+ tags: ["database", "edge"]
1933
+ },
1934
+ {
1935
+ domain: "console.neon.tech",
1936
+ specUrl: "https://raw.githubusercontent.com/neondatabase/neon-api-python/main/v2.json",
1937
+ auth: { type: "bearer", token: "${NEON_API_KEY}" },
1938
+ tags: ["database", "serverless-postgres"]
1939
+ }
1940
+ ];
1941
+ var COMMERCE_APIS = [
1942
+ {
1943
+ domain: "api.stripe.com",
1944
+ specUrl: "https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.json",
1945
+ auth: { type: "bearer", token: "${STRIPE_SECRET_KEY}" },
1946
+ tags: ["commerce", "payments"]
1947
+ }
1948
+ ];
1949
+ var COMMUNICATION_APIS = [
1950
+ {
1951
+ domain: "slack.com",
1952
+ specUrl: "https://raw.githubusercontent.com/slackapi/slack-api-specs/master/web-api/slack_web_openapi_v2.json",
1953
+ auth: { type: "bearer", token: "${SLACK_BOT_TOKEN}" },
1954
+ tags: ["communication", "messaging"]
1955
+ },
1956
+ {
1957
+ domain: "discord.com",
1958
+ specUrl: "https://raw.githubusercontent.com/discord/discord-api-spec/main/specs/openapi.json",
1959
+ auth: { type: "bearer", token: "${DISCORD_BOT_TOKEN}", prefix: "Bot" },
1960
+ tags: ["communication", "messaging"]
1961
+ },
1962
+ {
1963
+ domain: "api.twilio.com",
1964
+ specUrl: "https://raw.githubusercontent.com/twilio/twilio-oai/main/spec/json/twilio_api_v2010.json",
1965
+ auth: { type: "basic", token: "${TWILIO_ACCOUNT_SID}:${TWILIO_AUTH_TOKEN}" },
1966
+ tags: ["communication", "sms", "voice"]
1967
+ },
1968
+ {
1969
+ domain: "api.resend.com",
1970
+ specUrl: "https://raw.githubusercontent.com/resendlabs/resend-openapi/main/resend.yaml",
1971
+ auth: { type: "bearer", token: "${RESEND_API_KEY}" },
1972
+ tags: ["communication", "email"]
1973
+ }
1974
+ ];
1975
+ var EVM_METHODS = [
1976
+ { rpcMethod: "eth_blockNumber", description: "Returns the latest block number", resource: "blocks", fsOp: "list" },
1977
+ { rpcMethod: "eth_getBlockByNumber", description: "Returns block by number", resource: "blocks", fsOp: "read" },
1978
+ { rpcMethod: "eth_getBalance", description: "Returns account balance in wei", resource: "balances", fsOp: "read" },
1979
+ { rpcMethod: "eth_getTransactionByHash", description: "Returns transaction by hash", resource: "transactions", fsOp: "read" },
1980
+ { rpcMethod: "eth_getTransactionReceipt", description: "Returns transaction receipt", resource: "receipts", fsOp: "read" },
1981
+ { rpcMethod: "eth_call", description: "Executes a call without creating a transaction", resource: "calls", fsOp: "read" },
1982
+ { rpcMethod: "eth_estimateGas", description: "Estimates gas needed for a transaction", resource: "gas", fsOp: "read" },
1983
+ { rpcMethod: "eth_gasPrice", description: "Returns current gas price in wei" },
1984
+ { rpcMethod: "eth_chainId", description: "Returns the chain ID" },
1985
+ { rpcMethod: "eth_getCode", description: "Returns contract bytecode at address", resource: "code", fsOp: "read" },
1986
+ { rpcMethod: "eth_getLogs", description: "Returns logs matching a filter", resource: "logs", fsOp: "list" },
1987
+ { rpcMethod: "eth_getTransactionCount", description: "Returns the number of transactions sent from an address", resource: "nonces", fsOp: "read" },
1988
+ { rpcMethod: "net_version", description: "Returns the network ID" }
1989
+ ];
1990
+ var RPC_APIS = [
1991
+ // ── Free / Public RPC Providers ──────────────────────────────────
1992
+ {
1993
+ domain: "rpc.ankr.com",
1994
+ rpcDef: { url: "https://rpc.ankr.com/eth", convention: "evm", methods: EVM_METHODS },
1995
+ tags: ["blockchain", "ethereum", "free"]
1996
+ },
1997
+ {
1998
+ domain: "cloudflare-eth.com",
1999
+ rpcDef: { url: "https://cloudflare-eth.com", convention: "evm", methods: EVM_METHODS },
2000
+ tags: ["blockchain", "ethereum", "free"]
2001
+ },
2002
+ {
2003
+ domain: "ethereum-rpc.publicnode.com",
2004
+ rpcDef: { url: "https://ethereum-rpc.publicnode.com", convention: "evm", methods: EVM_METHODS },
2005
+ tags: ["blockchain", "ethereum", "free"]
2006
+ },
2007
+ // ── Auth-Required RPC Providers ──────────────────────────────────
2008
+ {
2009
+ domain: "eth-mainnet.g.alchemy.com",
2010
+ rpcDef: { url: "https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_API_KEY}", convention: "evm", methods: EVM_METHODS },
2011
+ tags: ["blockchain", "ethereum"]
2012
+ },
2013
+ {
2014
+ domain: "mainnet.infura.io",
2015
+ rpcDef: { url: "https://mainnet.infura.io/v3/${INFURA_API_KEY}", convention: "evm", methods: EVM_METHODS },
2016
+ tags: ["blockchain", "ethereum"]
2017
+ },
2018
+ // ── L2 / Alt Chains (Free) ───────────────────────────────────────
2019
+ {
2020
+ domain: "arb1.arbitrum.io",
2021
+ rpcDef: { url: "https://arb1.arbitrum.io/rpc", convention: "evm", methods: EVM_METHODS },
2022
+ tags: ["blockchain", "arbitrum", "l2", "free"]
2023
+ },
2024
+ {
2025
+ domain: "mainnet.optimism.io",
2026
+ rpcDef: { url: "https://mainnet.optimism.io", convention: "evm", methods: EVM_METHODS },
2027
+ tags: ["blockchain", "optimism", "l2", "free"]
2028
+ },
2029
+ {
2030
+ domain: "mainnet.base.org",
2031
+ rpcDef: { url: "https://mainnet.base.org", convention: "evm", methods: EVM_METHODS },
2032
+ tags: ["blockchain", "base", "l2", "free"]
2033
+ },
2034
+ {
2035
+ domain: "polygon-rpc.com",
2036
+ rpcDef: { url: "https://polygon-rpc.com", convention: "evm", methods: EVM_METHODS },
2037
+ tags: ["blockchain", "polygon", "free"]
2038
+ }
2039
+ ];
2040
+ var ALL_APIS = [
2041
+ ...FREE_APIS,
2042
+ ...FREEMIUM_APIS,
2043
+ ...DEVELOPER_TOOL_APIS,
2044
+ ...AI_APIS,
2045
+ ...CLOUD_APIS,
2046
+ ...PRODUCTIVITY_APIS,
2047
+ ...DEVOPS_APIS,
2048
+ ...DATABASE_APIS,
2049
+ ...COMMERCE_APIS,
2050
+ ...COMMUNICATION_APIS,
2051
+ ...RPC_APIS
2052
+ ];
2053
+
2054
+ // src/d1/sqlite-adapter.ts
2055
+ var BoundStatement = class {
2056
+ constructor(db, sql) {
2057
+ this.db = db;
2058
+ this.sql = sql;
2059
+ }
2060
+ params = [];
2061
+ bind(...values) {
2062
+ this.params = values;
2063
+ return this;
2064
+ }
2065
+ async first() {
2066
+ const stmt = this.db.prepare(this.sql);
2067
+ const row = stmt.get(...this.params);
2068
+ return row ?? null;
2069
+ }
2070
+ async all() {
2071
+ const stmt = this.db.prepare(this.sql);
2072
+ const rows = stmt.all(...this.params);
2073
+ return { results: rows, success: true };
2074
+ }
2075
+ async run() {
2076
+ const stmt = this.db.prepare(this.sql);
2077
+ const info = stmt.run(...this.params);
2078
+ return {
2079
+ success: true,
2080
+ changes: info.changes,
2081
+ lastRowId: Number(info.lastInsertRowid)
2082
+ };
2083
+ }
2084
+ };
2085
+ function createSqliteD1(db) {
2086
+ return {
2087
+ prepare(sql) {
2088
+ return new BoundStatement(db, sql);
2089
+ },
2090
+ async exec(sql) {
2091
+ db.exec(sql);
2092
+ }
2093
+ };
2094
+ }
2095
+
2096
+ // src/federation/d1-peer-store.ts
2097
+ function rowToPeer(row) {
2098
+ return {
2099
+ id: row.id,
2100
+ name: row.name,
2101
+ url: row.url,
2102
+ sharedSecret: row.shared_secret,
2103
+ status: row.status,
2104
+ advertisedDomains: JSON.parse(row.advertised_domains),
2105
+ lastSeen: row.last_seen,
2106
+ createdAt: row.created_at
2107
+ };
2108
+ }
2109
+ function rowToRule(row) {
2110
+ return {
2111
+ domain: row.domain,
2112
+ allow: row.allow === 1,
2113
+ peers: JSON.parse(row.peers),
2114
+ pricing: JSON.parse(row.pricing),
2115
+ ...row.rate_limit ? { rateLimit: JSON.parse(row.rate_limit) } : {},
2116
+ createdAt: row.created_at,
2117
+ updatedAt: row.updated_at
2118
+ };
2119
+ }
2120
+ var D1PeerStore = class {
2121
+ constructor(db) {
2122
+ this.db = db;
2123
+ }
2124
+ async initSchema() {
2125
+ await this.db.exec(`
2126
+ CREATE TABLE IF NOT EXISTS peers (
2127
+ id TEXT PRIMARY KEY,
2128
+ name TEXT NOT NULL,
2129
+ url TEXT NOT NULL,
2130
+ shared_secret TEXT NOT NULL,
2131
+ status TEXT NOT NULL DEFAULT 'active',
2132
+ advertised_domains TEXT NOT NULL DEFAULT '[]',
2133
+ last_seen INTEGER NOT NULL,
2134
+ created_at INTEGER NOT NULL
2135
+ )`);
2136
+ await this.db.exec(`
2137
+ CREATE TABLE IF NOT EXISTS lending_rules (
2138
+ domain TEXT PRIMARY KEY,
2139
+ allow INTEGER NOT NULL DEFAULT 1,
2140
+ peers TEXT NOT NULL DEFAULT '"*"',
2141
+ pricing TEXT NOT NULL DEFAULT '{"mode":"free"}',
2142
+ rate_limit TEXT,
2143
+ created_at INTEGER NOT NULL,
2144
+ updated_at INTEGER NOT NULL
2145
+ )`);
2146
+ }
2147
+ async getPeer(id) {
2148
+ const row = await this.db.prepare("SELECT * FROM peers WHERE id = ?").bind(id).first();
2149
+ return row ? rowToPeer(row) : null;
2150
+ }
2151
+ async putPeer(peer) {
2152
+ await this.db.prepare(
2153
+ `INSERT OR REPLACE INTO peers (id, name, url, shared_secret, status, advertised_domains, last_seen, created_at)
2154
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
2155
+ ).bind(
2156
+ peer.id,
2157
+ peer.name,
2158
+ peer.url,
2159
+ peer.sharedSecret,
2160
+ peer.status,
2161
+ JSON.stringify(peer.advertisedDomains),
2162
+ peer.lastSeen,
2163
+ peer.createdAt
2164
+ ).run();
2165
+ }
2166
+ async deletePeer(id) {
2167
+ await this.db.prepare("DELETE FROM peers WHERE id = ?").bind(id).run();
2168
+ }
2169
+ async listPeers() {
2170
+ const { results } = await this.db.prepare("SELECT * FROM peers WHERE status = 'active'").all();
2171
+ return results.map(rowToPeer);
2172
+ }
2173
+ async updateLastSeen(id, timestamp) {
2174
+ await this.db.prepare("UPDATE peers SET last_seen = ? WHERE id = ?").bind(timestamp, id).run();
2175
+ }
2176
+ async getRule(domain) {
2177
+ const row = await this.db.prepare("SELECT * FROM lending_rules WHERE domain = ?").bind(domain).first();
2178
+ return row ? rowToRule(row) : null;
2179
+ }
2180
+ async putRule(rule) {
2181
+ await this.db.prepare(
2182
+ `INSERT OR REPLACE INTO lending_rules (domain, allow, peers, pricing, rate_limit, created_at, updated_at)
2183
+ VALUES (?, ?, ?, ?, ?, ?, ?)`
2184
+ ).bind(
2185
+ rule.domain,
2186
+ rule.allow ? 1 : 0,
2187
+ JSON.stringify(rule.peers),
2188
+ JSON.stringify(rule.pricing),
2189
+ rule.rateLimit ? JSON.stringify(rule.rateLimit) : null,
2190
+ rule.createdAt,
2191
+ rule.updatedAt
2192
+ ).run();
2193
+ }
2194
+ async deleteRule(domain) {
2195
+ await this.db.prepare("DELETE FROM lending_rules WHERE domain = ?").bind(domain).run();
2196
+ }
2197
+ async listRules() {
2198
+ const { results } = await this.db.prepare("SELECT * FROM lending_rules").all();
2199
+ return results.map(rowToRule);
2200
+ }
2201
+ };
2202
+
2203
+ // src/tunnel/memory-store.ts
2204
+ var MemoryTunnelStore = class {
2205
+ records = /* @__PURE__ */ new Map();
2206
+ async get(id) {
2207
+ return this.records.get(id) ?? null;
2208
+ }
2209
+ async getByAgent(agentId) {
2210
+ for (const record of this.records.values()) {
2211
+ if (record.agentId === agentId && record.status === "active") {
2212
+ return record;
2213
+ }
2214
+ }
2215
+ return null;
2216
+ }
2217
+ async put(record) {
2218
+ this.records.set(record.id, record);
2219
+ }
2220
+ async delete(id) {
2221
+ this.records.delete(id);
2222
+ }
2223
+ async list() {
2224
+ return Array.from(this.records.values());
2225
+ }
2226
+ };
2227
+
2228
+ // src/tunnel/cloudflare-provider.ts
2229
+ var CF_API = "https://api.cloudflare.com/client/v4";
2230
+ var CloudflareTunnelProvider = class {
2231
+ constructor(accountId, apiToken, tunnelDomain, zoneId) {
2232
+ this.accountId = accountId;
2233
+ this.apiToken = apiToken;
2234
+ this.tunnelDomain = tunnelDomain;
2235
+ this.zoneId = zoneId;
2236
+ }
2237
+ async create(name, hostname) {
2238
+ const secretBytes = new Uint8Array(32);
2239
+ crypto.getRandomValues(secretBytes);
2240
+ const tunnelSecret = btoa(String.fromCharCode(...secretBytes));
2241
+ const tunnelRes = await this.cfFetch(
2242
+ `/accounts/${this.accountId}/cfd_tunnel`,
2243
+ {
2244
+ method: "POST",
2245
+ body: JSON.stringify({
2246
+ name,
2247
+ tunnel_secret: tunnelSecret
2248
+ })
2249
+ }
2250
+ );
2251
+ const tunnelId = tunnelRes.id;
2252
+ try {
2253
+ await this.cfFetch(`/zones/${this.zoneId}/dns_records`, {
2254
+ method: "POST",
2255
+ body: JSON.stringify({
2256
+ type: "CNAME",
2257
+ name: hostname,
2258
+ content: `${tunnelId}.cfargotunnel.com`,
2259
+ proxied: true
2260
+ })
2261
+ });
2262
+ await this.cfFetch(
2263
+ `/accounts/${this.accountId}/cfd_tunnel/${tunnelId}/configurations`,
2264
+ {
2265
+ method: "PUT",
2266
+ body: JSON.stringify({
2267
+ config: {
2268
+ ingress: [
2269
+ { hostname, service: "http://localhost:9090" },
2270
+ { service: "http_status:404" }
2271
+ ]
2272
+ }
2273
+ })
2274
+ }
2275
+ );
2276
+ const tokenRes = await this.cfFetch(
2277
+ `/accounts/${this.accountId}/cfd_tunnel/${tunnelId}/token`
2278
+ );
2279
+ return { tunnelId, tunnelToken: tokenRes };
2280
+ } catch (err) {
2281
+ await this.cfFetch(
2282
+ `/accounts/${this.accountId}/cfd_tunnel/${tunnelId}?cascade=true`,
2283
+ { method: "DELETE" }
2284
+ ).catch(() => {
2285
+ });
2286
+ throw err;
2287
+ }
2288
+ }
2289
+ async delete(tunnelId) {
2290
+ const dnsRecords = await this.cfFetch(
2291
+ `/zones/${this.zoneId}/dns_records?type=CNAME&content=${tunnelId}.cfargotunnel.com`
2292
+ );
2293
+ for (const record of dnsRecords) {
2294
+ await this.cfFetch(`/zones/${this.zoneId}/dns_records/${record.id}`, {
2295
+ method: "DELETE"
2296
+ });
2297
+ }
2298
+ await this.cfFetch(
2299
+ `/accounts/${this.accountId}/cfd_tunnel/${tunnelId}?cascade=true`,
2300
+ { method: "DELETE" }
2301
+ );
2302
+ }
2303
+ async cfFetch(path, init) {
2304
+ const res = await fetch(`${CF_API}${path}`, {
2305
+ ...init,
2306
+ headers: {
2307
+ Authorization: `Bearer ${this.apiToken}`,
2308
+ "Content-Type": "application/json",
2309
+ ...init?.headers
2310
+ }
2311
+ });
2312
+ const data = await res.json();
2313
+ if (!data.success) {
2314
+ const msg = data.errors.map((e) => e.message).join(", ");
2315
+ throw new Error(`Cloudflare API error: ${msg}`);
2316
+ }
2317
+ return data.result;
2318
+ }
2319
+ };
2320
+
2321
+ // src/http/routes/tunnels.ts
2322
+ var import_hono2 = require("hono");
2323
+ var import_nanoid = require("nanoid");
2324
+ function tunnelRoutes(options) {
2325
+ const { tunnelStore, tunnelProvider, tunnelDomain } = options;
2326
+ const app = new import_hono2.Hono();
2327
+ app.post("/create", async (c) => {
2328
+ const agent = c.get("agent");
2329
+ const body = await c.req.json().catch(() => ({}));
2330
+ const existing = await tunnelStore.getByAgent(agent.id);
2331
+ if (existing && existing.status === "active") {
2332
+ return c.json({
2333
+ tunnelId: existing.id,
2334
+ publicUrl: existing.publicUrl,
2335
+ message: "Tunnel already exists"
2336
+ });
2337
+ }
2338
+ const id = (0, import_nanoid.nanoid)(12);
2339
+ const hostname = `${id}.${tunnelDomain}`;
2340
+ const publicUrl = `https://${hostname}`;
2341
+ const { tunnelId, tunnelToken } = await tunnelProvider.create(
2342
+ `nkmc-${agent.id}-${id}`,
2343
+ hostname
2344
+ );
2345
+ const now = Date.now();
2346
+ await tunnelStore.put({
2347
+ id,
2348
+ agentId: agent.id,
2349
+ tunnelId,
2350
+ publicUrl,
2351
+ status: "active",
2352
+ createdAt: now,
2353
+ advertisedDomains: body.advertisedDomains ?? [],
2354
+ gatewayName: body.gatewayName,
2355
+ lastSeen: now
2356
+ });
2357
+ return c.json({ tunnelId: id, tunnelToken, publicUrl }, 201);
2358
+ });
2359
+ app.delete("/:id", async (c) => {
2360
+ const id = c.req.param("id");
2361
+ const agent = c.get("agent");
2362
+ const record = await tunnelStore.get(id);
2363
+ if (!record) return c.json({ error: "Tunnel not found" }, 404);
2364
+ if (record.agentId !== agent.id)
2365
+ return c.json({ error: "Not your tunnel" }, 403);
2366
+ await tunnelProvider.delete(record.tunnelId);
2367
+ await tunnelStore.delete(id);
2368
+ return c.json({ ok: true });
2369
+ });
2370
+ app.get("/", async (c) => {
2371
+ const agent = c.get("agent");
2372
+ const all = await tunnelStore.list();
2373
+ const mine = all.filter((t) => t.agentId === agent.id);
2374
+ return c.json({ tunnels: mine });
2375
+ });
2376
+ app.get("/discover", async (c) => {
2377
+ const domain = c.req.query("domain");
2378
+ const all = await tunnelStore.list();
2379
+ let results = all.filter((t) => t.status === "active");
2380
+ if (domain) {
2381
+ results = results.filter((t) => t.advertisedDomains.includes(domain));
2382
+ }
2383
+ return c.json({
2384
+ gateways: results.map((t) => ({
2385
+ id: t.id,
2386
+ name: t.gatewayName ?? `gateway-${t.id}`,
2387
+ publicUrl: t.publicUrl,
2388
+ advertisedDomains: t.advertisedDomains
2389
+ }))
2390
+ });
2391
+ });
2392
+ app.post("/heartbeat", async (c) => {
2393
+ const agent = c.get("agent");
2394
+ const body = await c.req.json();
2395
+ const record = await tunnelStore.getByAgent(agent.id);
2396
+ if (!record) return c.json({ error: "No active tunnel" }, 404);
2397
+ record.advertisedDomains = body.advertisedDomains ?? record.advertisedDomains;
2398
+ record.lastSeen = Date.now();
2399
+ await tunnelStore.put(record);
2400
+ return c.json({ ok: true });
2401
+ });
2402
+ return app;
2403
+ }
2404
+
2405
+ // src/index.ts
2406
+ var VERSION = "0.1.0";
2407
+ // Annotate the CommonJS export names for ESM import in node:
2408
+ 0 && (module.exports = {
2409
+ CloudflareTunnelProvider,
2410
+ Context7Backend,
2411
+ Context7Client,
2412
+ D1CredentialVault,
2413
+ D1MeterStore,
2414
+ D1PeerStore,
2415
+ D1RegistryStore,
2416
+ MemoryCredentialVault,
2417
+ MemoryMeterStore,
2418
+ MemoryRegistryStore,
2419
+ MemoryTunnelStore,
2420
+ OnboardPipeline,
2421
+ VERSION,
2422
+ VirtualFileBackend,
2423
+ checkAccess,
2424
+ createRegistryResolver,
2425
+ createSqliteD1,
2426
+ credentialRoutes,
2427
+ discoverFromApisGuru,
2428
+ extractDomainPath,
2429
+ lookupPricing,
2430
+ meter,
2431
+ parsePricingAnnotation,
2432
+ parseSkillMd,
2433
+ queryDnsTxt,
2434
+ skillToHttpConfig,
2435
+ tunnelRoutes
2436
+ });