@nordsym/apiclaw 2.2.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/dist/bin-http.js +0 -0
- package/dist/bin.bundled.js +79288 -0
- package/dist/gateway-client.d.ts.map +1 -1
- package/dist/gateway-client.js +24 -2
- package/dist/gateway-client.js.map +1 -1
- package/dist/index.bundled.js +61263 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/.claude/settings.local.json +0 -13
- package/.env.prod +0 -1
- package/apiclaw-README.md +0 -494
- package/convex/_generated/api.d.ts +0 -145
- package/convex/_generated/api.js +0 -23
- package/convex/_generated/dataModel.d.ts +0 -60
- package/convex/_generated/server.d.ts +0 -143
- package/convex/_generated/server.js +0 -93
- package/convex/_listWorkspaces.ts +0 -13
- package/convex/adminActivate.ts +0 -53
- package/convex/adminStats.ts +0 -306
- package/convex/agents.ts +0 -939
- package/convex/analytics.ts +0 -187
- package/convex/apiKeys.ts +0 -220
- package/convex/backfillAnalytics.ts +0 -272
- package/convex/backfillSearchLogs.ts +0 -35
- package/convex/billing.ts +0 -834
- package/convex/capabilities.ts +0 -157
- package/convex/chains.ts +0 -1318
- package/convex/credits.ts +0 -211
- package/convex/crons.ts +0 -65
- package/convex/debugFilestackLogs.ts +0 -16
- package/convex/debugGetToken.ts +0 -18
- package/convex/directCall.ts +0 -713
- package/convex/earnProgress.ts +0 -753
- package/convex/email.ts +0 -329
- package/convex/feedback.ts +0 -265
- package/convex/funnel.ts +0 -431
- package/convex/guards.ts +0 -174
- package/convex/http.ts +0 -3756
- package/convex/inbound.ts +0 -32
- package/convex/logs.ts +0 -701
- package/convex/migrateFilestack.ts +0 -81
- package/convex/migratePartnersProd.ts +0 -174
- package/convex/migratePratham.ts +0 -126
- package/convex/migrateProviderWorkspaces.ts +0 -175
- package/convex/mou.ts +0 -91
- package/convex/nurture.ts +0 -355
- package/convex/providerKeys.ts +0 -289
- package/convex/providers.ts +0 -1135
- package/convex/purchases.ts +0 -183
- package/convex/ratelimit.ts +0 -104
- package/convex/schema.ts +0 -926
- package/convex/searchLogs.ts +0 -265
- package/convex/seedAPILayerAPIs.ts +0 -191
- package/convex/seedDirectCallConfigs.ts +0 -336
- package/convex/seedPratham.ts +0 -149
- package/convex/spendAlerts.ts +0 -442
- package/convex/stripeActions.ts +0 -607
- package/convex/teams.ts +0 -243
- package/convex/telemetry.ts +0 -81
- package/convex/tsconfig.json +0 -25
- package/convex/updateAPIStatus.ts +0 -44
- package/convex/usage.ts +0 -260
- package/convex/usageReports.ts +0 -357
- package/convex/waitlist.ts +0 -55
- package/convex/webhooks.ts +0 -494
- package/convex/workspaceSettings.ts +0 -143
- package/convex/workspaces.ts +0 -1331
- package/convex.json +0 -3
- package/direct-test.mjs +0 -51
- package/email-templates/filestack-provider-outreach.html +0 -162
- package/email-templates/partnership-template.html +0 -116
- package/email-templates/pratham-draft-preview.txt +0 -57
- package/email-templates/pratham-partnership-draft.html +0 -141
- package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
- package/reports/pipeline/PIPELINE-REPORT.json +0 -153
- package/reports/pipeline/acquire_apisguru.json +0 -17
- package/reports/pipeline/capabilities.json +0 -38
- package/reports/pipeline/discover_azure_recursive.json +0 -1551
- package/reports/pipeline/discover_github.json +0 -25
- package/reports/pipeline/discover_github_repos.json +0 -49
- package/reports/pipeline/discover_swaggerhub.json +0 -24
- package/reports/pipeline/discover_well_known.json +0 -23
- package/reports/pipeline/fetch_specs.json +0 -19
- package/reports/pipeline/generate_providers.json +0 -14
- package/reports/pipeline/match_registry.json +0 -11
- package/reports/pipeline/parse_specs.json +0 -17
- package/reports/pipeline/promote_candidates.json +0 -34
- package/reports/pipeline/validate.json +0 -30
- package/reports/pipeline/validate_smoke_details.json +0 -3835
- package/reports/session-report-2026-04-05.html +0 -433
- package/seed-apis-direct.mjs +0 -106
- package/src/access-control.ts +0 -174
- package/src/adapters/base.ts +0 -364
- package/src/adapters/claude-desktop.ts +0 -41
- package/src/adapters/cline.ts +0 -88
- package/src/adapters/continue.ts +0 -91
- package/src/adapters/cursor.ts +0 -43
- package/src/adapters/custom.ts +0 -188
- package/src/adapters/detect.ts +0 -202
- package/src/adapters/index.ts +0 -47
- package/src/adapters/windsurf.ts +0 -44
- package/src/bin-http.ts +0 -45
- package/src/bin.ts +0 -34
- package/src/capability-router.ts +0 -331
- package/src/chainExecutor.ts +0 -730
- package/src/chainResolver.test.ts +0 -246
- package/src/chainResolver.ts +0 -658
- package/src/cli/commands/demo.ts +0 -109
- package/src/cli/commands/doctor.ts +0 -435
- package/src/cli/commands/index.ts +0 -9
- package/src/cli/commands/login.ts +0 -203
- package/src/cli/commands/mcp-install.ts +0 -373
- package/src/cli/commands/restore.ts +0 -333
- package/src/cli/commands/setup.ts +0 -297
- package/src/cli/commands/uninstall.ts +0 -240
- package/src/cli/index.ts +0 -148
- package/src/cli.ts +0 -370
- package/src/confirmation.ts +0 -296
- package/src/credentials.ts +0 -455
- package/src/credits.ts +0 -329
- package/src/crypto.ts +0 -75
- package/src/discovery.ts +0 -568
- package/src/enterprise/env.ts +0 -156
- package/src/enterprise/index.ts +0 -7
- package/src/enterprise/script-generator.ts +0 -481
- package/src/execute-dynamic.ts +0 -617
- package/src/execute.ts +0 -2386
- package/src/funnel-client.ts +0 -168
- package/src/funnel.test.ts +0 -187
- package/src/gateway-client.ts +0 -192
- package/src/hivr-whitelist.ts +0 -110
- package/src/http-api.ts +0 -286
- package/src/http-server-minimal.ts +0 -154
- package/src/index.ts +0 -2702
- package/src/intelligent-gateway.ts +0 -339
- package/src/mcp-analytics.ts +0 -156
- package/src/metered.ts +0 -149
- package/src/open-apis-generated.ts +0 -157
- package/src/open-apis.ts +0 -558
- package/src/postinstall.ts +0 -40
- package/src/product-whitelist.ts +0 -246
- package/src/proxy.ts +0 -36
- package/src/registration-guard.ts +0 -117
- package/src/session.ts +0 -129
- package/src/stripe.ts +0 -497
- package/src/telemetry.ts +0 -71
- package/src/test.ts +0 -135
- package/src/types/convex-api.d.ts +0 -20
- package/src/types/convex-api.ts +0 -21
- package/src/types.ts +0 -109
- package/src/ui/colors.ts +0 -219
- package/src/ui/errors.ts +0 -394
- package/src/ui/index.ts +0 -17
- package/src/ui/prompts.ts +0 -390
- package/src/ui/spinner.ts +0 -325
- package/src/utils/backup.ts +0 -224
- package/src/utils/config.ts +0 -318
- package/src/utils/os.ts +0 -124
- package/src/utils/paths.ts +0 -203
- package/src/webhook.ts +0 -107
- package/test-10-working.cjs +0 -97
- package/test-14-final.cjs +0 -96
- package/test-actual-handlers.ts +0 -92
- package/test-apilayer-all-14.ts +0 -249
- package/test-apilayer-fixed.ts +0 -248
- package/test-direct-endpoints.ts +0 -174
- package/test-exact-endpoints.ts +0 -144
- package/test-final.ts +0 -83
- package/test-full-routing.ts +0 -100
- package/test-handlers-correct.ts +0 -217
- package/test-numverify-key.ts +0 -41
- package/test-via-handlers.ts +0 -92
- package/test-worldnews.mjs +0 -26
- package/tsconfig.json +0 -20
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Generated open API providers — loaded from src/registry/generated-providers.json.
|
|
3
|
-
*
|
|
4
|
-
* This file is the runtime adapter that turns the static JSON artifact
|
|
5
|
-
* produced by scripts/pipeline/generate_providers.py into OpenAPIConfig
|
|
6
|
-
* instances. Manual providers in open-apis.ts always take precedence; this
|
|
7
|
-
* loader only adds providers whose ids don't already exist.
|
|
8
|
-
*
|
|
9
|
-
* Path templating uses simple {var} substitution from params; remaining
|
|
10
|
-
* params with `in: query` get appended to the query string.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { createRequire } from 'module';
|
|
14
|
-
import type { OpenAPIConfig, OpenAPIAction } from './open-apis.js';
|
|
15
|
-
|
|
16
|
-
const require = createRequire(import.meta.url);
|
|
17
|
-
|
|
18
|
-
interface GeneratedActionParam {
|
|
19
|
-
name: string;
|
|
20
|
-
in: 'path' | 'query' | 'header' | 'body' | string;
|
|
21
|
-
required?: boolean;
|
|
22
|
-
type?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface GeneratedAction {
|
|
26
|
-
method: 'GET' | 'POST';
|
|
27
|
-
pathTemplate: string;
|
|
28
|
-
operationId?: string;
|
|
29
|
-
summary?: string;
|
|
30
|
-
params?: GeneratedActionParam[];
|
|
31
|
-
requiresAuth?: boolean;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface GeneratedProvider {
|
|
35
|
-
id: string;
|
|
36
|
-
name: string;
|
|
37
|
-
description: string;
|
|
38
|
-
baseUrl: string;
|
|
39
|
-
host: string;
|
|
40
|
-
source: string;
|
|
41
|
-
sourceUrl: string;
|
|
42
|
-
callable: boolean;
|
|
43
|
-
matchKind: string;
|
|
44
|
-
matchConfidence: number;
|
|
45
|
-
actionCount: number;
|
|
46
|
-
actions: Record<string, GeneratedAction>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface GeneratedArtifact {
|
|
50
|
-
version: number;
|
|
51
|
-
generatedAt: number;
|
|
52
|
-
providerCount: number;
|
|
53
|
-
callableCount: number;
|
|
54
|
-
providers: GeneratedProvider[];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
let cachedArtifact: GeneratedArtifact | null = null;
|
|
58
|
-
|
|
59
|
-
function loadArtifact(): GeneratedArtifact {
|
|
60
|
-
if (cachedArtifact) return cachedArtifact;
|
|
61
|
-
try {
|
|
62
|
-
cachedArtifact = require('./registry/generated-providers.json') as GeneratedArtifact;
|
|
63
|
-
} catch {
|
|
64
|
-
cachedArtifact = {
|
|
65
|
-
version: 1,
|
|
66
|
-
generatedAt: 0,
|
|
67
|
-
providerCount: 0,
|
|
68
|
-
callableCount: 0,
|
|
69
|
-
providers: [],
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
return cachedArtifact;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function buildPath(template: string, params: Record<string, any>, paramDefs: GeneratedActionParam[]): string {
|
|
76
|
-
let path = template;
|
|
77
|
-
const pathVars = new Set<string>();
|
|
78
|
-
for (const def of paramDefs) {
|
|
79
|
-
if (def.in === 'path') pathVars.add(def.name);
|
|
80
|
-
}
|
|
81
|
-
// substitute {var}
|
|
82
|
-
path = path.replace(/\{([^}]+)\}/g, (_, name) => {
|
|
83
|
-
pathVars.add(name);
|
|
84
|
-
const v = params[name];
|
|
85
|
-
return v === undefined || v === null ? '' : encodeURIComponent(String(v));
|
|
86
|
-
});
|
|
87
|
-
// append query string params
|
|
88
|
-
const queryPairs: string[] = [];
|
|
89
|
-
for (const def of paramDefs) {
|
|
90
|
-
if (def.in !== 'query') continue;
|
|
91
|
-
const v = params[def.name];
|
|
92
|
-
if (v === undefined || v === null) continue;
|
|
93
|
-
queryPairs.push(`${encodeURIComponent(def.name)}=${encodeURIComponent(String(v))}`);
|
|
94
|
-
}
|
|
95
|
-
// also include any explicit params not declared in spec but passed at call time
|
|
96
|
-
for (const [k, v] of Object.entries(params)) {
|
|
97
|
-
if (pathVars.has(k)) continue;
|
|
98
|
-
if (paramDefs.some((d) => d.name === k && d.in === 'query')) continue;
|
|
99
|
-
if (k === '_body' || k === '_headers') continue;
|
|
100
|
-
if (v === undefined || v === null) continue;
|
|
101
|
-
if (typeof v === 'object') continue;
|
|
102
|
-
queryPairs.push(`${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`);
|
|
103
|
-
}
|
|
104
|
-
if (queryPairs.length) {
|
|
105
|
-
path += (path.includes('?') ? '&' : '?') + queryPairs.join('&');
|
|
106
|
-
}
|
|
107
|
-
return path;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function toOpenAPIConfig(p: GeneratedProvider): OpenAPIConfig {
|
|
111
|
-
const actions: Record<string, OpenAPIAction> = {};
|
|
112
|
-
for (const [aid, a] of Object.entries(p.actions)) {
|
|
113
|
-
const defs = a.params || [];
|
|
114
|
-
actions[aid] = {
|
|
115
|
-
method: a.method,
|
|
116
|
-
path: (params) => buildPath(a.pathTemplate, params || {}, defs),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
return {
|
|
120
|
-
name: p.name,
|
|
121
|
-
description: p.description || `${p.name} (auto-generated from ${p.source})`,
|
|
122
|
-
baseUrl: p.baseUrl.replace(/\/$/, ''),
|
|
123
|
-
actions,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Build the registry of generated providers ready to merge into openAPIs.
|
|
129
|
-
* Only providers marked callable=true are returned by default; pass
|
|
130
|
-
* { includeCandidates: true } to surface auth-gated entries as well.
|
|
131
|
-
*/
|
|
132
|
-
export function loadGeneratedProviders(opts: { includeCandidates?: boolean } = {}): Record<string, OpenAPIConfig> {
|
|
133
|
-
const artifact = loadArtifact();
|
|
134
|
-
const out: Record<string, OpenAPIConfig> = {};
|
|
135
|
-
for (const p of artifact.providers) {
|
|
136
|
-
if (!p.callable && !opts.includeCandidates) continue;
|
|
137
|
-
out[p.id] = toOpenAPIConfig(p);
|
|
138
|
-
}
|
|
139
|
-
return out;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function getGeneratedArtifactStats(): {
|
|
143
|
-
providerCount: number;
|
|
144
|
-
callableCount: number;
|
|
145
|
-
generatedAt: number;
|
|
146
|
-
} {
|
|
147
|
-
const a = loadArtifact();
|
|
148
|
-
return {
|
|
149
|
-
providerCount: a.providerCount,
|
|
150
|
-
callableCount: a.callableCount,
|
|
151
|
-
generatedAt: a.generatedAt,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function getGeneratedProviderMeta(id: string): GeneratedProvider | undefined {
|
|
156
|
-
return loadArtifact().providers.find((p) => p.id === id);
|
|
157
|
-
}
|
package/src/open-apis.ts
DELETED
|
@@ -1,558 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* APIClaw Open APIs - Free APIs that don't require authentication
|
|
3
|
-
* These are called directly but still logged for analytics
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { loadGeneratedProviders, getGeneratedArtifactStats } from './open-apis-generated.js';
|
|
7
|
-
|
|
8
|
-
export interface OpenAPIConfig {
|
|
9
|
-
name: string;
|
|
10
|
-
description: string;
|
|
11
|
-
baseUrl: string;
|
|
12
|
-
actions: Record<string, OpenAPIAction>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface OpenAPIAction {
|
|
16
|
-
method: 'GET' | 'POST';
|
|
17
|
-
path: (params: Record<string, any>) => string;
|
|
18
|
-
transform?: (data: any, params: Record<string, any>) => any;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Registry of open APIs
|
|
23
|
-
*/
|
|
24
|
-
export const openAPIs: Record<string, OpenAPIConfig> = {
|
|
25
|
-
// Frankfurter - Free currency exchange rates (ECB data)
|
|
26
|
-
frankfurter: {
|
|
27
|
-
name: 'Frankfurter',
|
|
28
|
-
description: 'Free currency exchange rates from European Central Bank',
|
|
29
|
-
baseUrl: 'https://api.frankfurter.app',
|
|
30
|
-
actions: {
|
|
31
|
-
convert: {
|
|
32
|
-
method: 'GET',
|
|
33
|
-
path: (p) => `/latest?from=${p.from || 'SEK'}&to=${p.to || 'USD'}&amount=${p.amount || 1}`,
|
|
34
|
-
transform: (data, params) => ({
|
|
35
|
-
from: params.from || 'SEK',
|
|
36
|
-
to: params.to || 'USD',
|
|
37
|
-
amount: data.amount,
|
|
38
|
-
result: data.rates?.[params.to || 'USD'],
|
|
39
|
-
rate: data.rates?.[params.to || 'USD'] / data.amount,
|
|
40
|
-
date: data.date,
|
|
41
|
-
}),
|
|
42
|
-
},
|
|
43
|
-
latest: {
|
|
44
|
-
method: 'GET',
|
|
45
|
-
path: (p) => `/latest?from=${p.base || 'SEK'}${p.symbols ? `&to=${p.symbols}` : ''}`,
|
|
46
|
-
transform: (data) => ({
|
|
47
|
-
base: data.base,
|
|
48
|
-
date: data.date,
|
|
49
|
-
rates: data.rates,
|
|
50
|
-
}),
|
|
51
|
-
},
|
|
52
|
-
historical: {
|
|
53
|
-
method: 'GET',
|
|
54
|
-
path: (p) => `/${p.date}?from=${p.base || 'SEK'}${p.symbols ? `&to=${p.symbols}` : ''}`,
|
|
55
|
-
transform: (data) => ({
|
|
56
|
-
base: data.base,
|
|
57
|
-
date: data.date,
|
|
58
|
-
rates: data.rates,
|
|
59
|
-
}),
|
|
60
|
-
},
|
|
61
|
-
currencies: {
|
|
62
|
-
method: 'GET',
|
|
63
|
-
path: () => '/currencies',
|
|
64
|
-
transform: (data) => ({ currencies: data }),
|
|
65
|
-
},
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
|
|
69
|
-
// CoinGecko - Free crypto data
|
|
70
|
-
coingecko: {
|
|
71
|
-
name: 'CoinGecko',
|
|
72
|
-
description: 'Free cryptocurrency data - prices, market cap, volume',
|
|
73
|
-
baseUrl: 'https://api.coingecko.com/api/v3',
|
|
74
|
-
actions: {
|
|
75
|
-
price: {
|
|
76
|
-
method: 'GET',
|
|
77
|
-
path: (p) => `/simple/price?ids=${p.ids || 'bitcoin'}&vs_currencies=${p.vs_currencies || 'usd'}`,
|
|
78
|
-
},
|
|
79
|
-
coins_list: {
|
|
80
|
-
method: 'GET',
|
|
81
|
-
path: () => '/coins/list',
|
|
82
|
-
},
|
|
83
|
-
coin: {
|
|
84
|
-
method: 'GET',
|
|
85
|
-
path: (p) => `/coins/${p.id}?localization=false&tickers=false&community_data=false&developer_data=false`,
|
|
86
|
-
transform: (data) => ({
|
|
87
|
-
id: data.id,
|
|
88
|
-
symbol: data.symbol,
|
|
89
|
-
name: data.name,
|
|
90
|
-
price_usd: data.market_data?.current_price?.usd,
|
|
91
|
-
market_cap_usd: data.market_data?.market_cap?.usd,
|
|
92
|
-
price_change_24h: data.market_data?.price_change_percentage_24h,
|
|
93
|
-
}),
|
|
94
|
-
},
|
|
95
|
-
trending: {
|
|
96
|
-
method: 'GET',
|
|
97
|
-
path: () => '/search/trending',
|
|
98
|
-
transform: (data) => ({
|
|
99
|
-
coins: data.coins?.map((c: any) => ({
|
|
100
|
-
id: c.item.id,
|
|
101
|
-
name: c.item.name,
|
|
102
|
-
symbol: c.item.symbol,
|
|
103
|
-
market_cap_rank: c.item.market_cap_rank,
|
|
104
|
-
})),
|
|
105
|
-
}),
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
// IP-API - Free IP geolocation
|
|
111
|
-
ipapi: {
|
|
112
|
-
name: 'IP-API',
|
|
113
|
-
description: 'Free IP geolocation - get location from IP address',
|
|
114
|
-
baseUrl: 'http://ip-api.com',
|
|
115
|
-
actions: {
|
|
116
|
-
lookup: {
|
|
117
|
-
method: 'GET',
|
|
118
|
-
path: (p) => `/json/${p.ip || ''}`,
|
|
119
|
-
transform: (data) => ({
|
|
120
|
-
ip: data.query,
|
|
121
|
-
country: data.country,
|
|
122
|
-
countryCode: data.countryCode,
|
|
123
|
-
region: data.regionName,
|
|
124
|
-
city: data.city,
|
|
125
|
-
lat: data.lat,
|
|
126
|
-
lon: data.lon,
|
|
127
|
-
isp: data.isp,
|
|
128
|
-
timezone: data.timezone,
|
|
129
|
-
}),
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
|
|
134
|
-
// Kroki - Diagrams as code
|
|
135
|
-
kroki: {
|
|
136
|
-
name: 'Kroki',
|
|
137
|
-
description: 'Generate diagrams from text. Supports: mermaid, d2, plantuml, graphviz, c4plantuml, blockdiag, bpmn. D2 is SVG-only.',
|
|
138
|
-
baseUrl: 'https://kroki.io',
|
|
139
|
-
actions: {
|
|
140
|
-
render: {
|
|
141
|
-
method: 'POST',
|
|
142
|
-
path: () => '', // Custom handling below
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Merge generated providers into the registry.
|
|
150
|
-
* Curated entries with custom transforms take precedence on id collisions.
|
|
151
|
-
*/
|
|
152
|
-
const _generatedProviders = loadGeneratedProviders();
|
|
153
|
-
for (const [id, cfg] of Object.entries(_generatedProviders)) {
|
|
154
|
-
if (!(id in openAPIs)) {
|
|
155
|
-
openAPIs[id] = cfg;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Generic passthrough provider — proxy any open API without pre-configuration.
|
|
161
|
-
* Enables all 22,000+ indexed APIs to be callable on demand.
|
|
162
|
-
*
|
|
163
|
-
* Usage: call_api("generic", "request", {
|
|
164
|
-
* url: "https://api.example.com/endpoint",
|
|
165
|
-
* method: "GET", // optional, default GET
|
|
166
|
-
* query: { key: "value" }, // optional query params
|
|
167
|
-
* body: { key: "value" }, // optional body (POST/PUT/PATCH)
|
|
168
|
-
* headers: { "X-Foo": "bar" }, // optional extra headers
|
|
169
|
-
* })
|
|
170
|
-
*/
|
|
171
|
-
|
|
172
|
-
const SSRF_BLOCKLIST = [
|
|
173
|
-
/^https?:\/\/localhost/i,
|
|
174
|
-
/^https?:\/\/127\./,
|
|
175
|
-
/^https?:\/\/10\./,
|
|
176
|
-
/^https?:\/\/172\.(1[6-9]|2\d|3[01])\./,
|
|
177
|
-
/^https?:\/\/192\.168\./,
|
|
178
|
-
/^https?:\/\/169\.254\./, // link-local / AWS metadata
|
|
179
|
-
/^https?:\/\/metadata\.google/i,
|
|
180
|
-
/^https?:\/\/\[::1\]/, // IPv6 loopback
|
|
181
|
-
];
|
|
182
|
-
|
|
183
|
-
function isSsrfBlocked(url: string): boolean {
|
|
184
|
-
return SSRF_BLOCKLIST.some(r => r.test(url));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async function executeGeneric(
|
|
188
|
-
action: string,
|
|
189
|
-
params: Record<string, any>
|
|
190
|
-
): Promise<{ success: boolean; provider: string; action: string; data?: any; error?: string }> {
|
|
191
|
-
const url: string = params.url;
|
|
192
|
-
if (!url) {
|
|
193
|
-
return { success: false, provider: 'generic', action, error: 'Missing required param: url' };
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (isSsrfBlocked(url)) {
|
|
197
|
-
return { success: false, provider: 'generic', action, error: 'URL is not allowed (private/internal range)' };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const method = (params.method || 'GET').toUpperCase();
|
|
201
|
-
const query: Record<string, string> = params.query || {};
|
|
202
|
-
const body = params.body;
|
|
203
|
-
const extraHeaders: Record<string, string> = params.headers || {};
|
|
204
|
-
|
|
205
|
-
// Build URL with query params
|
|
206
|
-
let finalUrl = url;
|
|
207
|
-
const qKeys = Object.keys(query);
|
|
208
|
-
if (qKeys.length > 0) {
|
|
209
|
-
const sep = url.includes('?') ? '&' : '?';
|
|
210
|
-
finalUrl = url + sep + qKeys.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(query[k])}`).join('&');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const headers: Record<string, string> = {
|
|
214
|
-
'Accept': 'application/json',
|
|
215
|
-
'User-Agent': 'APIClaw/1.0 (+https://apiclaw.cloud)',
|
|
216
|
-
...extraHeaders,
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
const fetchOpts: RequestInit = { method, headers };
|
|
220
|
-
|
|
221
|
-
if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
222
|
-
headers['Content-Type'] = 'application/json';
|
|
223
|
-
fetchOpts.body = JSON.stringify(body);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
const res = await fetch(finalUrl, fetchOpts);
|
|
228
|
-
const contentType = res.headers.get('content-type') || '';
|
|
229
|
-
|
|
230
|
-
let data: any;
|
|
231
|
-
if (contentType.includes('application/json')) {
|
|
232
|
-
data = await res.json();
|
|
233
|
-
} else {
|
|
234
|
-
data = await res.text();
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (!res.ok) {
|
|
238
|
-
return {
|
|
239
|
-
success: false,
|
|
240
|
-
provider: 'generic',
|
|
241
|
-
action,
|
|
242
|
-
error: `HTTP ${res.status}: ${res.statusText}`,
|
|
243
|
-
data,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return { success: true, provider: 'generic', action, data };
|
|
248
|
-
} catch (e: any) {
|
|
249
|
-
return { success: false, provider: 'generic', action, error: e.message || 'Request failed' };
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Check if a provider is an open API
|
|
255
|
-
*/
|
|
256
|
-
export function isOpenAPI(providerId: string): boolean {
|
|
257
|
-
return providerId === 'generic' || providerId in openAPIs;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Get available actions for an open API
|
|
262
|
-
*/
|
|
263
|
-
export function getOpenAPIActions(providerId: string): string[] {
|
|
264
|
-
return Object.keys(openAPIs[providerId]?.actions || {});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Execute an open API call
|
|
269
|
-
*/
|
|
270
|
-
export async function executeOpenAPI(
|
|
271
|
-
providerId: string,
|
|
272
|
-
action: string,
|
|
273
|
-
params: Record<string, any>
|
|
274
|
-
): Promise<{ success: boolean; provider: string; action: string; data?: any; error?: string }> {
|
|
275
|
-
// Generic passthrough — proxy any open API on demand
|
|
276
|
-
if (providerId === 'generic') {
|
|
277
|
-
return await executeGeneric(action, params);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const config = openAPIs[providerId];
|
|
281
|
-
|
|
282
|
-
if (!config) {
|
|
283
|
-
return {
|
|
284
|
-
success: false,
|
|
285
|
-
provider: providerId,
|
|
286
|
-
action,
|
|
287
|
-
error: `Unknown open API: ${providerId}`,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const actionConfig = config.actions[action];
|
|
292
|
-
|
|
293
|
-
if (!actionConfig) {
|
|
294
|
-
return {
|
|
295
|
-
success: false,
|
|
296
|
-
provider: providerId,
|
|
297
|
-
action,
|
|
298
|
-
error: `Unknown action '${action}' for ${providerId}. Available: ${Object.keys(config.actions).join(', ')}`,
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
|
|
304
|
-
// Special handling for Kroki - use POST to get actual image data
|
|
305
|
-
if (providerId === 'kroki') {
|
|
306
|
-
const type = params.type || 'mermaid';
|
|
307
|
-
let format = params.format || 'svg';
|
|
308
|
-
const diagram = params.diagram || '';
|
|
309
|
-
|
|
310
|
-
if (!diagram) {
|
|
311
|
-
return {
|
|
312
|
-
success: false,
|
|
313
|
-
provider: providerId,
|
|
314
|
-
action,
|
|
315
|
-
error: 'Missing required param: diagram',
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// D2 only supports SVG
|
|
320
|
-
if (type === 'd2' && format !== 'svg') {
|
|
321
|
-
format = 'svg';
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
try {
|
|
325
|
-
const response = await fetch(`https://kroki.io/${type}/${format}`, {
|
|
326
|
-
method: 'POST',
|
|
327
|
-
headers: { 'Content-Type': 'text/plain' },
|
|
328
|
-
body: diagram,
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
if (!response.ok) {
|
|
332
|
-
const err = await response.text();
|
|
333
|
-
return {
|
|
334
|
-
success: false,
|
|
335
|
-
provider: providerId,
|
|
336
|
-
action,
|
|
337
|
-
error: `Kroki error: ${err}`,
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// For SVG, return the content directly; for binary, return base64
|
|
342
|
-
if (format === 'svg') {
|
|
343
|
-
const svg = await response.text();
|
|
344
|
-
return {
|
|
345
|
-
success: true,
|
|
346
|
-
provider: providerId,
|
|
347
|
-
action,
|
|
348
|
-
data: {
|
|
349
|
-
type,
|
|
350
|
-
format: 'svg',
|
|
351
|
-
content: svg,
|
|
352
|
-
content_type: 'image/svg+xml',
|
|
353
|
-
supported_types: ['mermaid', 'd2', 'plantuml', 'graphviz', 'c4plantuml', 'blockdiag', 'bpmn'],
|
|
354
|
-
},
|
|
355
|
-
};
|
|
356
|
-
} else {
|
|
357
|
-
const buffer = await response.arrayBuffer();
|
|
358
|
-
const base64 = Buffer.from(buffer).toString('base64');
|
|
359
|
-
return {
|
|
360
|
-
success: true,
|
|
361
|
-
provider: providerId,
|
|
362
|
-
action,
|
|
363
|
-
data: {
|
|
364
|
-
type,
|
|
365
|
-
format,
|
|
366
|
-
content_base64: base64,
|
|
367
|
-
content_type: format === 'png' ? 'image/png' : 'application/pdf',
|
|
368
|
-
},
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
} catch (e: any) {
|
|
372
|
-
return {
|
|
373
|
-
success: false,
|
|
374
|
-
provider: providerId,
|
|
375
|
-
action,
|
|
376
|
-
error: e.message || 'Kroki request failed',
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
const url = config.baseUrl + actionConfig.path(params);
|
|
382
|
-
const response = await fetch(url, {
|
|
383
|
-
method: actionConfig.method,
|
|
384
|
-
headers: {
|
|
385
|
-
'Accept': 'application/json',
|
|
386
|
-
'User-Agent': 'APIClaw/1.0 (+https://apiclaw.cloud)',
|
|
387
|
-
},
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
if (!response.ok) {
|
|
391
|
-
return {
|
|
392
|
-
success: false,
|
|
393
|
-
provider: providerId,
|
|
394
|
-
action,
|
|
395
|
-
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const ct = response.headers.get('content-type') || '';
|
|
400
|
-
let data: any;
|
|
401
|
-
if (ct.includes('application/json') || ct.includes('+json')) {
|
|
402
|
-
data = await response.json();
|
|
403
|
-
} else {
|
|
404
|
-
const text = await response.text();
|
|
405
|
-
try {
|
|
406
|
-
data = JSON.parse(text);
|
|
407
|
-
} catch {
|
|
408
|
-
data = { raw: text.length > 4096 ? text.slice(0, 4096) + '…' : text, contentType: ct };
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (actionConfig.transform) {
|
|
413
|
-
data = actionConfig.transform(data, params);
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
success: true,
|
|
418
|
-
provider: providerId,
|
|
419
|
-
action,
|
|
420
|
-
data,
|
|
421
|
-
};
|
|
422
|
-
} catch (e: any) {
|
|
423
|
-
return {
|
|
424
|
-
success: false,
|
|
425
|
-
provider: providerId,
|
|
426
|
-
action,
|
|
427
|
-
error: e.message || 'Request failed',
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
/**
|
|
433
|
-
* Resolve the full URL that executeOpenAPI would fetch for a given call.
|
|
434
|
-
* Used by the gateway client to pass the target URL to the Intelligent Gateway.
|
|
435
|
-
*
|
|
436
|
-
* Returns undefined if the provider/action is unknown or needs special handling
|
|
437
|
-
* that can't be expressed as a simple URL (e.g. Kroki POST with body).
|
|
438
|
-
*/
|
|
439
|
-
export function getOpenAPIBaseUrl(
|
|
440
|
-
providerId: string,
|
|
441
|
-
action: string,
|
|
442
|
-
params: Record<string, any>,
|
|
443
|
-
): string | undefined {
|
|
444
|
-
// Generic passthrough -- the caller already supplies the full URL
|
|
445
|
-
if (providerId === 'generic') {
|
|
446
|
-
return params.url as string | undefined;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const config = openAPIs[providerId];
|
|
450
|
-
if (!config) return undefined;
|
|
451
|
-
|
|
452
|
-
const actionConfig = config.actions[action];
|
|
453
|
-
if (!actionConfig) return undefined;
|
|
454
|
-
|
|
455
|
-
// Kroki uses a POST body; return the URL but the gateway will also need the body
|
|
456
|
-
if (providerId === 'kroki') {
|
|
457
|
-
const type = params.type || 'mermaid';
|
|
458
|
-
let format = params.format || 'svg';
|
|
459
|
-
if (type === 'd2' && format !== 'svg') format = 'svg';
|
|
460
|
-
return `${config.baseUrl}/${type}/${format}`;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
try {
|
|
464
|
-
return config.baseUrl + actionConfig.path(params);
|
|
465
|
-
} catch {
|
|
466
|
-
return undefined;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* List all open APIs with their actions
|
|
472
|
-
*/
|
|
473
|
-
export function listOpenAPIs(): { provider: string; name: string; description: string; actions: string[] }[] {
|
|
474
|
-
const curated = Object.entries(openAPIs).map(([id, config]) => ({
|
|
475
|
-
provider: id,
|
|
476
|
-
name: config.name,
|
|
477
|
-
description: config.description,
|
|
478
|
-
actions: Object.keys(config.actions),
|
|
479
|
-
}));
|
|
480
|
-
|
|
481
|
-
return [
|
|
482
|
-
{
|
|
483
|
-
provider: 'generic',
|
|
484
|
-
name: 'Generic Passthrough',
|
|
485
|
-
description: 'Proxy any open/public API on demand. Supply url, method, query, body, headers. No pre-configuration needed — unlocks all 22,000+ indexed APIs.',
|
|
486
|
-
actions: ['request'],
|
|
487
|
-
},
|
|
488
|
-
...curated,
|
|
489
|
-
];
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Stats about the generated provider artifact.
|
|
494
|
-
*/
|
|
495
|
-
export function getOpenAPIStats(): {
|
|
496
|
-
curatedProviders: number;
|
|
497
|
-
generatedProviders: number;
|
|
498
|
-
generatedCallable: number;
|
|
499
|
-
totalProviders: number;
|
|
500
|
-
generatedAt: number;
|
|
501
|
-
} {
|
|
502
|
-
const stats = getGeneratedArtifactStats();
|
|
503
|
-
// openAPIs already contains curated + merged generated; subtract to get the curated count
|
|
504
|
-
const curatedOnly = Object.keys(openAPIs).length - Object.keys(_generatedProviders).length;
|
|
505
|
-
return {
|
|
506
|
-
curatedProviders: curatedOnly,
|
|
507
|
-
generatedProviders: stats.providerCount,
|
|
508
|
-
generatedCallable: stats.callableCount,
|
|
509
|
-
totalProviders: Object.keys(openAPIs).length,
|
|
510
|
-
generatedAt: stats.generatedAt,
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
/**
|
|
515
|
-
* APIClaw operates three tiers. This function returns the unified count
|
|
516
|
-
* across all of them, so the dashboard, landing page, and homepage badge
|
|
517
|
-
* always cite a coherent number.
|
|
518
|
-
*
|
|
519
|
-
* - Tier 1 (Discovery): the indexed registry — searchable, free, no auth
|
|
520
|
-
* - Tier 2 (Open API): callable without keys, free
|
|
521
|
-
* - Tier 3 (Direct Call): premium, APIClaw owns the keys
|
|
522
|
-
*
|
|
523
|
-
* The Direct Call count is intentionally a constant here. It's small,
|
|
524
|
-
* changes rarely, and lives canonically in the Apiclaw MOC. If it ever
|
|
525
|
-
* grows past a handful, replace this constant with a Convex query.
|
|
526
|
-
*/
|
|
527
|
-
export const DIRECT_CALL_PROVIDERS = 19; // mirrors Apiclaw MOC: Tier 3
|
|
528
|
-
export const DIRECT_CALL_SUB_APIS = 27; // APILayer sub-APIs under one provider
|
|
529
|
-
|
|
530
|
-
export function getAPIClawTotalStats(opts: { registryIndexed?: number } = {}): {
|
|
531
|
-
tier1_discovery_indexed: number;
|
|
532
|
-
tier2_openapi_curated: number;
|
|
533
|
-
tier2_openapi_generated: number;
|
|
534
|
-
tier2_openapi_generated_callable: number;
|
|
535
|
-
tier2_openapi_callable_total: number;
|
|
536
|
-
tier3_direct_call_providers: number;
|
|
537
|
-
tier3_direct_call_sub_apis: number;
|
|
538
|
-
total_callable: number;
|
|
539
|
-
total_indexed_or_callable: number;
|
|
540
|
-
generatedArtifactAt: number;
|
|
541
|
-
} {
|
|
542
|
-
const stats = getOpenAPIStats();
|
|
543
|
-
const registryIndexed = opts.registryIndexed ?? 22393; // canonical from src/registry/apis.json
|
|
544
|
-
const tier2Callable = stats.curatedProviders + stats.generatedCallable;
|
|
545
|
-
const tier3 = DIRECT_CALL_PROVIDERS + DIRECT_CALL_SUB_APIS;
|
|
546
|
-
return {
|
|
547
|
-
tier1_discovery_indexed: registryIndexed,
|
|
548
|
-
tier2_openapi_curated: stats.curatedProviders,
|
|
549
|
-
tier2_openapi_generated: stats.generatedProviders,
|
|
550
|
-
tier2_openapi_generated_callable: stats.generatedCallable,
|
|
551
|
-
tier2_openapi_callable_total: tier2Callable,
|
|
552
|
-
tier3_direct_call_providers: DIRECT_CALL_PROVIDERS,
|
|
553
|
-
tier3_direct_call_sub_apis: DIRECT_CALL_SUB_APIS,
|
|
554
|
-
total_callable: tier2Callable + tier3,
|
|
555
|
-
total_indexed_or_callable: registryIndexed + stats.generatedProviders + tier3,
|
|
556
|
-
generatedArtifactAt: stats.generatedAt,
|
|
557
|
-
};
|
|
558
|
-
}
|