@nordsym/apiclaw 1.2.2 → 1.2.3
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/AGENTS.md +50 -33
- package/README.md +22 -12
- package/SOUL.md +60 -19
- package/STATUS.md +91 -169
- package/convex/_generated/api.d.ts +6 -0
- package/convex/directCall.ts +598 -0
- package/convex/providers.ts +341 -26
- package/convex/schema.ts +87 -0
- package/convex/usage.ts +260 -0
- package/convex/waitlist.ts +55 -0
- package/data/combined-02-26.json +22102 -0
- package/data/night-expansion-02-26-06-batch2.json +1898 -0
- package/data/night-expansion-02-26-06-batch3.json +1410 -0
- package/data/night-expansion-02-26-06.json +3146 -0
- package/data/night-expansion-02-26-full.json +9726 -0
- package/data/night-expansion-02-26-v2.json +330 -0
- package/data/night-expansion-02-26.json +171 -0
- package/dist/crypto.d.ts +7 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +67 -0
- package/dist/crypto.js.map +1 -0
- package/dist/execute-dynamic.d.ts +116 -0
- package/dist/execute-dynamic.d.ts.map +1 -0
- package/dist/execute-dynamic.js +456 -0
- package/dist/execute-dynamic.js.map +1 -0
- package/dist/execute.d.ts +2 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +35 -5
- package/dist/execute.js.map +1 -1
- package/dist/index.js +33 -4
- package/dist/index.js.map +1 -1
- package/dist/registry/apis.json +2081 -3
- package/docs/PRD-customer-key-passthrough.md +184 -0
- package/landing/public/badges/available-on-apiclaw.svg +14 -0
- package/landing/scripts/generate-stats.js +75 -4
- package/landing/src/app/admin/page.tsx +1 -1
- package/landing/src/app/api/auth/magic-link/route.ts +1 -1
- package/landing/src/app/api/auth/session/route.ts +1 -1
- package/landing/src/app/api/auth/verify/route.ts +1 -1
- package/landing/src/app/api/og/route.tsx +5 -3
- package/landing/src/app/docs/page.tsx +5 -4
- package/landing/src/app/earn/page.tsx +14 -11
- package/landing/src/app/globals.css +16 -15
- package/landing/src/app/layout.tsx +2 -2
- package/landing/src/app/page.tsx +425 -254
- package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +600 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +583 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +301 -0
- package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +659 -0
- package/landing/src/app/providers/dashboard/[apiId]/page.tsx +381 -0
- package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +418 -0
- package/landing/src/app/providers/dashboard/layout.tsx +292 -0
- package/landing/src/app/providers/dashboard/page.tsx +353 -290
- package/landing/src/app/providers/register/page.tsx +87 -10
- package/landing/src/components/AiClientDropdown.tsx +85 -0
- package/landing/src/components/ConfigHelperModal.tsx +113 -0
- package/landing/src/components/HeroTabs.tsx +187 -0
- package/landing/src/components/ShareIntegrationModal.tsx +198 -0
- package/landing/src/hooks/useDashboardData.ts +53 -1
- package/landing/src/lib/apis.json +46554 -174
- package/landing/src/lib/convex-client.ts +22 -3
- package/landing/src/lib/stats.json +4 -4
- package/landing/tsconfig.tsbuildinfo +1 -1
- package/night-expansion-02-26-06-batch2.py +368 -0
- package/night-expansion-02-26-06-batch3.py +299 -0
- package/night-expansion-02-26-06.py +756 -0
- package/package.json +1 -1
- package/scripts/bulk-add-public-apis-v2.py +418 -0
- package/scripts/night-expansion-02-26-v2.py +296 -0
- package/scripts/night-expansion-02-26.py +890 -0
- package/scripts/seed-complete-api.js +181 -0
- package/scripts/seed-demo-api.sh +44 -0
- package/src/crypto.ts +75 -0
- package/src/execute-dynamic.ts +589 -0
- package/src/execute.ts +41 -5
- package/src/index.ts +38 -4
- package/src/registry/apis.json +2081 -3
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Seed a complete API with Direct Call and usage data for APIClaw
|
|
4
|
+
* Usage: node scripts/seed-complete-api.js
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const CONVEX_URL = "https://brilliant-puffin-712.eu-west-1.convex.cloud";
|
|
8
|
+
|
|
9
|
+
async function convexMutation(path, args) {
|
|
10
|
+
const res = await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
11
|
+
method: "POST",
|
|
12
|
+
headers: { "Content-Type": "application/json" },
|
|
13
|
+
body: JSON.stringify({ path, args }),
|
|
14
|
+
});
|
|
15
|
+
return res.json();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function convexQuery(path, args) {
|
|
19
|
+
const res = await fetch(`${CONVEX_URL}/api/query`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { "Content-Type": "application/json" },
|
|
22
|
+
body: JSON.stringify({ path, args }),
|
|
23
|
+
});
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function main() {
|
|
28
|
+
console.log("🦞 Seeding complete API for APIClaw...\n");
|
|
29
|
+
|
|
30
|
+
// Step 1: Create the API listing
|
|
31
|
+
console.log("1. Creating API listing...");
|
|
32
|
+
const apiResult = await convexMutation("providers:registerProvider", {
|
|
33
|
+
provider: {
|
|
34
|
+
name: "NordSym AB",
|
|
35
|
+
email: "gustav@nordsym.com",
|
|
36
|
+
website: "https://nordsym.com",
|
|
37
|
+
},
|
|
38
|
+
api: {
|
|
39
|
+
name: "WeatherStack API",
|
|
40
|
+
description: "Real-time weather data for any location worldwide. Get current conditions, forecasts, historical data, and location lookups. Supports 1M+ locations with high accuracy data from multiple sources.",
|
|
41
|
+
category: "Weather",
|
|
42
|
+
docsUrl: "https://weatherstack.com/documentation",
|
|
43
|
+
pricingModel: "freemium",
|
|
44
|
+
pricingNotes: "Free tier: 250 calls/month. Pro: 50k calls/month.",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
console.log(" API created:", apiResult);
|
|
49
|
+
|
|
50
|
+
if (!apiResult || apiResult.error) {
|
|
51
|
+
console.error("Failed to create API:", apiResult);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const providerId = apiResult.value?.providerId || apiResult.providerId;
|
|
56
|
+
const apiId = apiResult.value?.apiId || apiResult.apiId;
|
|
57
|
+
|
|
58
|
+
if (!providerId || !apiId) {
|
|
59
|
+
console.error("Failed to get IDs from API result:", apiResult);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(" Provider ID:", providerId);
|
|
64
|
+
console.log(" API ID:", apiId);
|
|
65
|
+
|
|
66
|
+
// Step 2: Add Direct Call configuration
|
|
67
|
+
console.log("\n2. Setting up Direct Call config...");
|
|
68
|
+
const directCallResult = await convexMutation("directCall:saveDirectCallConfig", {
|
|
69
|
+
providerId: providerId,
|
|
70
|
+
apiId: apiId,
|
|
71
|
+
baseUrl: "https://api.weatherstack.com",
|
|
72
|
+
authType: "api_key",
|
|
73
|
+
authHeader: "access_key",
|
|
74
|
+
authPrefix: "",
|
|
75
|
+
encryptedMasterKey: "demo_key_encrypted_xxxxx", // Placeholder
|
|
76
|
+
rateLimitPerUser: 60,
|
|
77
|
+
rateLimitPerDay: 1000,
|
|
78
|
+
pricePerRequest: 0, // Free for now
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
console.log(" Direct Call config:", directCallResult);
|
|
82
|
+
|
|
83
|
+
if (!directCallResult || directCallResult.error) {
|
|
84
|
+
console.error("Failed to create Direct Call config:", directCallResult);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const directCallId = directCallResult;
|
|
89
|
+
|
|
90
|
+
// Step 3: Add actions
|
|
91
|
+
console.log("\n3. Adding actions...");
|
|
92
|
+
|
|
93
|
+
const actions = [
|
|
94
|
+
{
|
|
95
|
+
name: "get_current",
|
|
96
|
+
displayName: "Get Current Weather",
|
|
97
|
+
description: "Get current weather conditions for a location",
|
|
98
|
+
method: "GET",
|
|
99
|
+
path: "/current",
|
|
100
|
+
params: [
|
|
101
|
+
{ name: "query", type: "string", required: true, description: "Location (city name, coordinates, IP)", in: "query" },
|
|
102
|
+
{ name: "units", type: "string", required: false, description: "Units: m (metric), f (fahrenheit), s (scientific)", in: "query", default: "m" },
|
|
103
|
+
],
|
|
104
|
+
responseMapping: [
|
|
105
|
+
{ name: "temperature", path: "current.temperature" },
|
|
106
|
+
{ name: "description", path: "current.weather_descriptions[0]" },
|
|
107
|
+
{ name: "humidity", path: "current.humidity" },
|
|
108
|
+
{ name: "wind_speed", path: "current.wind_speed" },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "get_forecast",
|
|
113
|
+
displayName: "Get Weather Forecast",
|
|
114
|
+
description: "Get weather forecast for upcoming days",
|
|
115
|
+
method: "GET",
|
|
116
|
+
path: "/forecast",
|
|
117
|
+
params: [
|
|
118
|
+
{ name: "query", type: "string", required: true, description: "Location", in: "query" },
|
|
119
|
+
{ name: "forecast_days", type: "number", required: false, description: "Number of days (1-14)", in: "query", default: 7 },
|
|
120
|
+
{ name: "hourly", type: "boolean", required: false, description: "Include hourly data", in: "query", default: false },
|
|
121
|
+
],
|
|
122
|
+
responseMapping: [
|
|
123
|
+
{ name: "forecast", path: "forecast" },
|
|
124
|
+
{ name: "location", path: "location.name" },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "lookup_location",
|
|
129
|
+
displayName: "Lookup Location",
|
|
130
|
+
description: "Search for a location by name",
|
|
131
|
+
method: "GET",
|
|
132
|
+
path: "/autocomplete",
|
|
133
|
+
params: [
|
|
134
|
+
{ name: "query", type: "string", required: true, description: "Location search query", in: "query" },
|
|
135
|
+
],
|
|
136
|
+
responseMapping: [
|
|
137
|
+
{ name: "results", path: "results" },
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
for (const action of actions) {
|
|
143
|
+
const result = await convexMutation("directCall:saveAction", {
|
|
144
|
+
directCallId,
|
|
145
|
+
...action,
|
|
146
|
+
enabled: true,
|
|
147
|
+
});
|
|
148
|
+
console.log(` Action "${action.name}":`, result ? "✓" : "✗");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Step 4: Publish the Direct Call config
|
|
152
|
+
console.log("\n4. Publishing Direct Call...");
|
|
153
|
+
await convexMutation("directCall:publishDirectCall", { id: directCallId });
|
|
154
|
+
console.log(" Published ✓");
|
|
155
|
+
|
|
156
|
+
// Step 5: Add some usage data
|
|
157
|
+
console.log("\n5. Seeding usage data...");
|
|
158
|
+
const now = Date.now();
|
|
159
|
+
const demoAgents = ["agent_claude_prod", "agent_cursor_dev", "agent_windsurf_1", "agent_claude_user123", "agent_aider_beta"];
|
|
160
|
+
|
|
161
|
+
for (let i = 0; i < 50; i++) {
|
|
162
|
+
const agentId = demoAgents[Math.floor(Math.random() * demoAgents.length)];
|
|
163
|
+
const actionName = actions[Math.floor(Math.random() * actions.length)].name;
|
|
164
|
+
|
|
165
|
+
await convexMutation("usage:logUsage", {
|
|
166
|
+
userId: agentId,
|
|
167
|
+
providerId,
|
|
168
|
+
directCallId,
|
|
169
|
+
actionName,
|
|
170
|
+
success: Math.random() > 0.02, // 98% success rate
|
|
171
|
+
latencyMs: Math.floor(Math.random() * 200) + 50,
|
|
172
|
+
creditsUsed: 0,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
console.log(" Added 50 usage entries ✓");
|
|
176
|
+
|
|
177
|
+
console.log("\n✅ Complete! API is now live with Direct Call and usage data.");
|
|
178
|
+
console.log("\nView in dashboard: https://apiclaw.nordsym.com/providers/dashboard");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Seed a complete demo API with Direct Call for NordSym
|
|
3
|
+
|
|
4
|
+
CONVEX_URL="https://brilliant-puffin-712.eu-west-1.convex.cloud"
|
|
5
|
+
|
|
6
|
+
# First, get Gustav's provider ID by listing APIs
|
|
7
|
+
echo "Looking up NordSym provider..."
|
|
8
|
+
|
|
9
|
+
# Get session token from Gustav's browser (he needs to provide this)
|
|
10
|
+
# Or we can create the API directly using the registerProvider mutation
|
|
11
|
+
|
|
12
|
+
echo "Creating demo API: Stripe Payment Link Generator..."
|
|
13
|
+
|
|
14
|
+
# Create a well-structured demo API
|
|
15
|
+
curl -s "$CONVEX_URL/api/mutation" \
|
|
16
|
+
-H "Content-Type: application/json" \
|
|
17
|
+
-d '{
|
|
18
|
+
"path": "providers:registerProvider",
|
|
19
|
+
"args": {
|
|
20
|
+
"provider": {
|
|
21
|
+
"name": "NordSym AB",
|
|
22
|
+
"email": "gustav@nordsym.com",
|
|
23
|
+
"website": "https://nordsym.com"
|
|
24
|
+
},
|
|
25
|
+
"api": {
|
|
26
|
+
"name": "Stripe Payment Links",
|
|
27
|
+
"description": "Generate Stripe payment links dynamically. Create one-time or subscription payment links for any amount and currency. Perfect for invoicing, donations, product sales, and custom checkout flows.",
|
|
28
|
+
"category": "Payments",
|
|
29
|
+
"docsUrl": "https://stripe.com/docs/payment-links",
|
|
30
|
+
"pricingModel": "freemium"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}' | jq .
|
|
34
|
+
|
|
35
|
+
echo ""
|
|
36
|
+
echo "API created! Now set up Direct Call via the dashboard:"
|
|
37
|
+
echo "https://apiclaw.nordsym.com/providers/dashboard"
|
|
38
|
+
echo ""
|
|
39
|
+
echo "Direct Call config for Stripe:"
|
|
40
|
+
echo " Base URL: https://api.stripe.com/v1"
|
|
41
|
+
echo " Auth Type: Bearer Token"
|
|
42
|
+
echo " Auth Header: Authorization"
|
|
43
|
+
echo " Master Key: sk_test_... (your Stripe secret key)"
|
|
44
|
+
echo " Rate Limit: 60/min, 1000/day"
|
package/src/crypto.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
|
|
2
|
+
|
|
3
|
+
const ENCRYPTION_KEY = process.env.APICLAW_KEY_ENCRYPTION_SECRET;
|
|
4
|
+
|
|
5
|
+
// Validate key exists and is correct length
|
|
6
|
+
function getKey(): Buffer {
|
|
7
|
+
if (!ENCRYPTION_KEY) {
|
|
8
|
+
throw new Error('APICLAW_KEY_ENCRYPTION_SECRET not set');
|
|
9
|
+
}
|
|
10
|
+
// Key should be 32 bytes for AES-256
|
|
11
|
+
const key = Buffer.from(ENCRYPTION_KEY, 'hex');
|
|
12
|
+
if (key.length !== 32) {
|
|
13
|
+
throw new Error('APICLAW_KEY_ENCRYPTION_SECRET must be 64 hex chars (32 bytes)');
|
|
14
|
+
}
|
|
15
|
+
return key;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function encryptKey(plainKey: string): string {
|
|
19
|
+
const key = getKey();
|
|
20
|
+
const iv = randomBytes(16);
|
|
21
|
+
const cipher = createCipheriv('aes-256-gcm', key, iv);
|
|
22
|
+
const encrypted = Buffer.concat([cipher.update(plainKey, 'utf8'), cipher.final()]);
|
|
23
|
+
const tag = cipher.getAuthTag();
|
|
24
|
+
return `${iv.toString('hex')}:${tag.toString('hex')}:${encrypted.toString('hex')}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function decryptKey(encryptedKey: string): string {
|
|
28
|
+
const key = getKey();
|
|
29
|
+
const [ivHex, tagHex, dataHex] = encryptedKey.split(':');
|
|
30
|
+
if (!ivHex || !tagHex || !dataHex) {
|
|
31
|
+
throw new Error('Invalid encrypted key format');
|
|
32
|
+
}
|
|
33
|
+
const decipher = createDecipheriv('aes-256-gcm', key, Buffer.from(ivHex, 'hex'));
|
|
34
|
+
decipher.setAuthTag(Buffer.from(tagHex, 'hex'));
|
|
35
|
+
const decrypted = Buffer.concat([
|
|
36
|
+
decipher.update(Buffer.from(dataHex, 'hex')),
|
|
37
|
+
decipher.final()
|
|
38
|
+
]);
|
|
39
|
+
return decrypted.toString('utf8');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// SSRF Prevention
|
|
43
|
+
export function validateBaseUrl(url: string): { valid: boolean; error?: string } {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = new URL(url);
|
|
46
|
+
|
|
47
|
+
if (parsed.protocol !== 'https:') {
|
|
48
|
+
return { valid: false, error: 'URL must use HTTPS' };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const host = parsed.hostname.toLowerCase();
|
|
52
|
+
|
|
53
|
+
const blockedPatterns = [
|
|
54
|
+
/^127\./,
|
|
55
|
+
/^10\./,
|
|
56
|
+
/^192\.168\./,
|
|
57
|
+
/^172\.(1[6-9]|2[0-9]|3[0-1])\./,
|
|
58
|
+
/^localhost$/,
|
|
59
|
+
/^0\.0\.0\.0$/,
|
|
60
|
+
/^::1$/,
|
|
61
|
+
/\.local$/,
|
|
62
|
+
/\.internal$/,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
for (const pattern of blockedPatterns) {
|
|
66
|
+
if (pattern.test(host)) {
|
|
67
|
+
return { valid: false, error: 'Internal/private URLs not allowed' };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return { valid: true };
|
|
72
|
+
} catch {
|
|
73
|
+
return { valid: false, error: 'Invalid URL format' };
|
|
74
|
+
}
|
|
75
|
+
}
|