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