@nordsym/apiclaw 1.0.0 → 1.1.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/AGENTS.md +74 -0
- package/HEARTBEAT.md +4 -0
- package/IDENTITY.md +22 -0
- package/README.md +193 -202
- package/SOUL.md +36 -0
- package/STATUS.md +237 -0
- package/TOOLS.md +36 -0
- package/USER.md +17 -0
- package/{backend/convex → convex}/_generated/api.d.ts +12 -6
- package/convex/analytics.ts +90 -0
- package/convex/credits.ts +211 -0
- package/convex/http.ts +578 -0
- package/convex/providers.ts +516 -0
- package/convex/purchases.ts +183 -0
- package/convex/ratelimit.ts +104 -0
- package/convex/schema.ts +220 -0
- package/convex/telemetry.ts +81 -0
- package/convex.json +3 -0
- package/dist/credentials.d.ts +19 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +158 -0
- package/dist/credentials.js.map +1 -0
- package/dist/credits.d.ts +14 -11
- package/dist/credits.d.ts.map +1 -1
- package/dist/credits.js +151 -99
- package/dist/credits.js.map +1 -1
- package/dist/discovery.d.ts +7 -16
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +33 -40
- package/dist/discovery.js.map +1 -1
- package/dist/execute.d.ts +19 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +285 -0
- package/dist/execute.js.map +1 -0
- package/dist/index.js +175 -31
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +6 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +19 -0
- package/dist/proxy.js.map +1 -0
- package/dist/registry/apis.json +95362 -202
- package/dist/registry/apis_expanded.json +100853 -0
- package/dist/stripe.d.ts +68 -0
- package/dist/stripe.d.ts.map +1 -0
- package/dist/stripe.js +196 -0
- package/dist/stripe.js.map +1 -0
- package/dist/telemetry.d.ts +28 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +50 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/test.d.ts +3 -2
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +105 -75
- package/dist/test.js.map +1 -1
- package/dist/types.d.ts +0 -28
- package/dist/types.d.ts.map +1 -1
- package/dist/webhook.d.ts +2 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +90 -0
- package/dist/webhook.js.map +1 -0
- package/landing/DESIGN.md +343 -0
- package/landing/package-lock.json +1196 -7
- package/landing/package.json +5 -1
- package/landing/public/android-chrome-192x192.png +0 -0
- package/landing/public/android-chrome-512x512.png +0 -0
- package/landing/public/apple-touch-icon.png +0 -0
- package/landing/public/demo.gif +0 -0
- package/landing/public/demo.mp4 +0 -0
- package/landing/public/favicon-16x16.png +0 -0
- package/landing/public/favicon-32x32.png +0 -0
- package/landing/public/favicon.ico +0 -0
- package/landing/public/favicon.svg +3 -0
- package/landing/public/icon.svg +47 -0
- package/landing/public/logo-mono.svg +37 -0
- package/landing/public/logo-simple.svg +45 -0
- package/landing/public/logo.svg +84 -0
- package/landing/public/og-template.html +184 -0
- package/landing/public/site.webmanifest +31 -0
- package/landing/scripts/generate-assets.js +284 -0
- package/landing/scripts/generate-pngs.js +48 -0
- package/landing/scripts/generate-stats.js +42 -0
- package/landing/src/app/admin/page.tsx +348 -0
- package/landing/src/app/api/auth/magic-link/route.ts +73 -0
- package/landing/src/app/api/auth/session/route.ts +38 -0
- package/landing/src/app/api/auth/verify/route.ts +43 -0
- package/landing/src/app/api/og/route.tsx +84 -0
- package/landing/src/app/globals.css +439 -100
- package/landing/src/app/layout.tsx +37 -7
- package/landing/src/app/page.tsx +627 -552
- package/landing/src/app/providers/dashboard/login/page.tsx +176 -0
- package/landing/src/app/providers/dashboard/page.tsx +589 -0
- package/landing/src/app/providers/dashboard/verify/page.tsx +106 -0
- package/landing/src/app/providers/layout.tsx +14 -0
- package/landing/src/app/providers/page.tsx +402 -0
- package/landing/src/app/providers/register/page.tsx +670 -0
- package/landing/src/components/ProviderDashboard.tsx +794 -0
- package/landing/src/hooks/useDashboardData.ts +99 -0
- package/landing/src/lib/apis.json +116054 -0
- package/landing/src/lib/convex-client.ts +106 -0
- package/landing/src/lib/mock-data.ts +285 -0
- package/landing/src/lib/stats.json +6 -0
- package/landing/tailwind.config.ts +12 -11
- package/landing/tsconfig.tsbuildinfo +1 -0
- package/package.json +21 -20
- package/scripts/SYMBOT-FIX.md +238 -0
- package/scripts/demo-simulation.py +177 -0
- package/scripts/expand-more.py +502 -0
- package/scripts/expand-registry.py +434 -0
- package/scripts/history-sanitizer.ts +272 -0
- package/scripts/mass-scrape.py +1308 -0
- package/scripts/sync-and-deploy.sh +36 -0
- package/src/credentials.ts +177 -0
- package/src/credits.ts +190 -122
- package/src/discovery.ts +45 -58
- package/src/execute.ts +350 -0
- package/src/index.ts +184 -32
- package/src/proxy.ts +24 -0
- package/src/registry/apis.json +95362 -202
- package/src/registry/apis_expanded.json +100853 -0
- package/src/stripe.ts +243 -0
- package/src/telemetry.ts +71 -0
- package/src/test.ts +127 -89
- package/src/types.ts +0 -34
- package/src/webhook.ts +107 -0
- package/.github/ISSUE_TEMPLATE/add-api.yml +0 -123
- package/BRIEFING.md +0 -30
- package/backend/convex/apiKeys.ts +0 -75
- package/backend/convex/purchases.ts +0 -74
- package/backend/convex/schema.ts +0 -45
- package/backend/convex/transactions.ts +0 -57
- package/backend/convex/users.ts +0 -94
- package/backend/package-lock.json +0 -521
- package/backend/package.json +0 -15
- package/dist/registry/parse_apis.py +0 -146
- package/dist/revenuecat.d.ts +0 -61
- package/dist/revenuecat.d.ts.map +0 -1
- package/dist/revenuecat.js +0 -166
- package/dist/revenuecat.js.map +0 -1
- package/dist/webhooks/revenuecat.d.ts +0 -48
- package/dist/webhooks/revenuecat.d.ts.map +0 -1
- package/dist/webhooks/revenuecat.js +0 -119
- package/dist/webhooks/revenuecat.js.map +0 -1
- package/docs/revenuecat-setup.md +0 -89
- package/landing/src/app/api/keys/route.ts +0 -71
- package/landing/src/app/api/log/route.ts +0 -37
- package/landing/src/app/api/stats/route.ts +0 -37
- package/landing/src/app/page.tsx.bak +0 -567
- package/landing/src/components/AddKeyModal.tsx +0 -159
- package/newsletter-template.html +0 -71
- package/outreach/OUTREACH-SYSTEM.md +0 -211
- package/outreach/email-template.html +0 -179
- package/outreach/targets.md +0 -133
- package/src/registry/parse_apis.py +0 -146
- package/src/revenuecat.ts +0 -239
- package/src/webhooks/revenuecat.ts +0 -187
- /package/{backend/convex → convex}/README.md +0 -0
- /package/{backend/convex → convex}/_generated/api.js +0 -0
- /package/{backend/convex → convex}/_generated/dataModel.d.ts +0 -0
- /package/{backend/convex → convex}/_generated/server.d.ts +0 -0
- /package/{backend/convex → convex}/_generated/server.js +0 -0
- /package/{backend/convex → convex}/tsconfig.json +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Sync API registry stats and deploy to Vercel
|
|
3
|
+
|
|
4
|
+
cd "$(dirname "$0")/.."
|
|
5
|
+
|
|
6
|
+
echo "📊 Updating registry count..."
|
|
7
|
+
python3 << 'EOF'
|
|
8
|
+
import json
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
|
|
11
|
+
with open('src/registry/apis.json', 'r') as f:
|
|
12
|
+
registry = json.load(f)
|
|
13
|
+
|
|
14
|
+
registry['count'] = len(registry['apis'])
|
|
15
|
+
registry['lastUpdated'] = datetime.now().strftime('%Y-%m-%d')
|
|
16
|
+
|
|
17
|
+
with open('src/registry/apis.json', 'w') as f:
|
|
18
|
+
json.dump(registry, f, indent=2)
|
|
19
|
+
|
|
20
|
+
print(f"✓ Registry: {registry['count']} APIs")
|
|
21
|
+
EOF
|
|
22
|
+
|
|
23
|
+
echo "📦 Copying apis.json to landing..."
|
|
24
|
+
cp src/registry/apis.json landing/src/lib/apis.json
|
|
25
|
+
|
|
26
|
+
echo "📈 Generating stats for frontend..."
|
|
27
|
+
cd landing
|
|
28
|
+
node scripts/generate-stats.js
|
|
29
|
+
|
|
30
|
+
echo "🗑️ Clearing build cache..."
|
|
31
|
+
rm -rf .next
|
|
32
|
+
|
|
33
|
+
echo "🚀 Deploying to Vercel..."
|
|
34
|
+
npx vercel --prod --force --yes
|
|
35
|
+
|
|
36
|
+
echo "✅ Done! Check https://apiclaw.nordsym.com"
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Real credential providers for APIClaw Connected tier
|
|
2
|
+
// Reads from environment variables or ~/.secrets/
|
|
3
|
+
|
|
4
|
+
import { readFileSync, existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
import { APICredentials } from './types.js';
|
|
8
|
+
|
|
9
|
+
interface ProviderCredential {
|
|
10
|
+
type: 'basic' | 'api_key' | 'bearer';
|
|
11
|
+
get(): APICredentials | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Load env file helper
|
|
15
|
+
function loadEnvFile(filename: string): Record<string, string> {
|
|
16
|
+
const paths = [
|
|
17
|
+
join(homedir(), '.secrets', filename),
|
|
18
|
+
join(process.cwd(), filename),
|
|
19
|
+
join(process.cwd(), '.env.local'),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
for (const path of paths) {
|
|
23
|
+
if (existsSync(path)) {
|
|
24
|
+
const content = readFileSync(path, 'utf-8');
|
|
25
|
+
const vars: Record<string, string> = {};
|
|
26
|
+
for (const line of content.split('\n')) {
|
|
27
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
28
|
+
if (match) {
|
|
29
|
+
vars[match[1].trim()] = match[2].trim().replace(/^["']|["']$/g, '');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return vars;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Provider credential getters
|
|
39
|
+
const providers: Record<string, ProviderCredential> = {
|
|
40
|
+
'46elks': {
|
|
41
|
+
type: 'basic',
|
|
42
|
+
get(): APICredentials | null {
|
|
43
|
+
const env = loadEnvFile('46elks.env');
|
|
44
|
+
const user = env.ELKS_API_USER || process.env.ELKS_API_USER;
|
|
45
|
+
const pass = env.ELKS_API_PASSWORD || process.env.ELKS_API_PASSWORD;
|
|
46
|
+
|
|
47
|
+
if (!user || !pass) return null;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
type: 'basic',
|
|
51
|
+
username: user,
|
|
52
|
+
password: pass,
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
twilio: {
|
|
58
|
+
type: 'basic',
|
|
59
|
+
get(): APICredentials | null {
|
|
60
|
+
const env = loadEnvFile('twilio.env');
|
|
61
|
+
const sid = env.TWILIO_ACCOUNT_SID || process.env.TWILIO_ACCOUNT_SID;
|
|
62
|
+
const token = env.TWILIO_AUTH_TOKEN || process.env.TWILIO_AUTH_TOKEN;
|
|
63
|
+
|
|
64
|
+
if (!sid || !token) return null;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
type: 'basic',
|
|
68
|
+
username: sid,
|
|
69
|
+
password: token,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// Real credential providers
|
|
75
|
+
resend: {
|
|
76
|
+
type: 'api_key',
|
|
77
|
+
get(): APICredentials | null {
|
|
78
|
+
const env = loadEnvFile('resend.env');
|
|
79
|
+
const key = env.RESEND_API_KEY || process.env.RESEND_API_KEY;
|
|
80
|
+
if (key) {
|
|
81
|
+
return { type: 'api_key', api_key: key };
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
brave_search: {
|
|
88
|
+
type: 'api_key',
|
|
89
|
+
get(): APICredentials | null {
|
|
90
|
+
const env = loadEnvFile('brave.env');
|
|
91
|
+
const key = env.BRAVE_API_KEY || process.env.BRAVE_API_KEY;
|
|
92
|
+
if (key) {
|
|
93
|
+
return { type: 'api_key', api_key: key };
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
openrouter: {
|
|
100
|
+
type: 'bearer',
|
|
101
|
+
get(): APICredentials | null {
|
|
102
|
+
const env = loadEnvFile('openrouter.env');
|
|
103
|
+
const key = env.OPENROUTER_API_KEY || process.env.OPENROUTER_API_KEY;
|
|
104
|
+
if (key) {
|
|
105
|
+
return { type: 'bearer', api_key: key };
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
elevenlabs: {
|
|
112
|
+
type: 'api_key',
|
|
113
|
+
get(): APICredentials | null {
|
|
114
|
+
const env = loadEnvFile('elevenlabs.env');
|
|
115
|
+
const key = env.ELEVENLABS_API_KEY || process.env.ELEVENLABS_API_KEY;
|
|
116
|
+
if (key) {
|
|
117
|
+
return { type: 'api_key', api_key: key };
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get real credentials for a provider
|
|
126
|
+
* Returns null if provider not supported
|
|
127
|
+
*/
|
|
128
|
+
export function getCredentials(providerId: string): APICredentials | null {
|
|
129
|
+
const provider = providers[providerId];
|
|
130
|
+
if (!provider) return null;
|
|
131
|
+
return provider.get();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if a provider has real (non-demo) credentials available
|
|
136
|
+
*/
|
|
137
|
+
export function hasRealCredentials(providerId: string): boolean {
|
|
138
|
+
if (providerId === '46elks') {
|
|
139
|
+
const env = loadEnvFile('46elks.env');
|
|
140
|
+
return !!(env.ELKS_API_USER || process.env.ELKS_API_USER);
|
|
141
|
+
}
|
|
142
|
+
if (providerId === 'twilio') {
|
|
143
|
+
const env = loadEnvFile('twilio.env');
|
|
144
|
+
return !!(env.TWILIO_ACCOUNT_SID || process.env.TWILIO_ACCOUNT_SID);
|
|
145
|
+
}
|
|
146
|
+
if (providerId === 'resend') {
|
|
147
|
+
const env = loadEnvFile('resend.env');
|
|
148
|
+
return !!(env.RESEND_API_KEY || process.env.RESEND_API_KEY);
|
|
149
|
+
}
|
|
150
|
+
if (providerId === 'brave_search') {
|
|
151
|
+
const env = loadEnvFile('brave.env');
|
|
152
|
+
return !!(env.BRAVE_API_KEY || process.env.BRAVE_API_KEY);
|
|
153
|
+
}
|
|
154
|
+
if (providerId === 'openrouter') {
|
|
155
|
+
const env = loadEnvFile('openrouter.env');
|
|
156
|
+
return !!(env.OPENROUTER_API_KEY || process.env.OPENROUTER_API_KEY);
|
|
157
|
+
}
|
|
158
|
+
if (providerId === 'elevenlabs') {
|
|
159
|
+
const env = loadEnvFile('elevenlabs.env');
|
|
160
|
+
return !!(env.ELEVENLABS_API_KEY || process.env.ELEVENLABS_API_KEY);
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* List all supported providers
|
|
167
|
+
*/
|
|
168
|
+
export function listProviders(): string[] {
|
|
169
|
+
return Object.keys(providers);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get credential type for a provider
|
|
174
|
+
*/
|
|
175
|
+
export function getCredentialType(providerId: string): string | null {
|
|
176
|
+
return providers[providerId]?.type || null;
|
|
177
|
+
}
|
package/src/credits.ts
CHANGED
|
@@ -1,46 +1,73 @@
|
|
|
1
|
-
// Credit system for
|
|
2
|
-
//
|
|
1
|
+
// Credit system for APIClaw
|
|
2
|
+
// Supports both in-memory (dev) and Convex (production) backends
|
|
3
3
|
|
|
4
|
-
import { AgentCredits, Purchase, APICredentials, UsageRecord
|
|
4
|
+
import { AgentCredits, Purchase, APICredentials, UsageRecord } from './types.js';
|
|
5
|
+
import { getCredentials, hasRealCredentials } from './credentials.js';
|
|
5
6
|
import { randomUUID } from 'crypto';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
'
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}),
|
|
32
|
-
'elevenlabs': () => ({
|
|
33
|
-
type: 'api_key',
|
|
34
|
-
api_key: `${randomUUID().replace(/-/g, '')}`
|
|
35
|
-
})
|
|
7
|
+
|
|
8
|
+
// Storage backend type
|
|
9
|
+
type StorageBackend = 'memory' | 'convex';
|
|
10
|
+
|
|
11
|
+
// Configuration
|
|
12
|
+
const BACKEND: StorageBackend = process.env.APICLAW_BACKEND === 'convex' ? 'convex' : 'memory';
|
|
13
|
+
const CONVEX_URL = process.env.CONVEX_URL || '';
|
|
14
|
+
const CONVEX_DEPLOY_KEY = process.env.CONVEX_DEPLOY_KEY || '';
|
|
15
|
+
|
|
16
|
+
// In-memory stores (for local development)
|
|
17
|
+
const agentCreditsStore: Map<string, AgentCredits> = new Map();
|
|
18
|
+
const purchasesStore: Map<string, Purchase> = new Map();
|
|
19
|
+
const usageStore: Map<string, UsageRecord> = new Map();
|
|
20
|
+
|
|
21
|
+
// Provider that have real credentials available
|
|
22
|
+
const REAL_CREDENTIAL_PROVIDERS = ['46elks', 'twilio'];
|
|
23
|
+
|
|
24
|
+
// Credits per dollar by provider
|
|
25
|
+
const CREDITS_PER_DOLLAR: Record<string, number> = {
|
|
26
|
+
'46elks': 30, // ~30 SMS per dollar
|
|
27
|
+
'twilio': 25, // ~25 SMS per dollar
|
|
28
|
+
'resend': 1000, // ~1000 emails per dollar
|
|
29
|
+
'brave_search': 200, // ~200 searches per dollar
|
|
30
|
+
'openrouter': 100, // ~100k tokens per dollar (varies by model)
|
|
31
|
+
'elevenlabs': 3333 // ~3333 characters per dollar
|
|
36
32
|
};
|
|
37
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Calculate credits based on provider pricing
|
|
36
|
+
*/
|
|
37
|
+
function calculateCredits(providerId: string, amountUsd: number): number {
|
|
38
|
+
return Math.floor(amountUsd * (CREDITS_PER_DOLLAR[providerId] || 100));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generate credentials for a provider
|
|
43
|
+
* Returns real credentials for supported providers, mock for others
|
|
44
|
+
*/
|
|
45
|
+
function generateCredentials(providerId: string): APICredentials {
|
|
46
|
+
// Try to get real credentials first
|
|
47
|
+
const realCreds = getCredentials(providerId);
|
|
48
|
+
if (realCreds && hasRealCredentials(providerId)) {
|
|
49
|
+
console.log(`[APIClaw] Using REAL credentials for ${providerId}`);
|
|
50
|
+
return realCreds;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Fall back to mock credentials
|
|
54
|
+
console.log(`[APIClaw] Using MOCK credentials for ${providerId}`);
|
|
55
|
+
return realCreds || {
|
|
56
|
+
type: 'api_key',
|
|
57
|
+
api_key: `mock_${providerId}_${randomUUID().slice(0, 8)}`
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// In-Memory Backend (for development)
|
|
63
|
+
// =============================================================================
|
|
64
|
+
|
|
38
65
|
/**
|
|
39
66
|
* Get or create agent credits account
|
|
40
67
|
*/
|
|
41
68
|
export function getAgentCredits(agentId: string): AgentCredits {
|
|
42
|
-
if (!
|
|
43
|
-
|
|
69
|
+
if (!agentCreditsStore.has(agentId)) {
|
|
70
|
+
agentCreditsStore.set(agentId, {
|
|
44
71
|
agent_id: agentId,
|
|
45
72
|
balance_usd: 0,
|
|
46
73
|
currency: 'USD',
|
|
@@ -48,7 +75,7 @@ export function getAgentCredits(agentId: string): AgentCredits {
|
|
|
48
75
|
updated_at: new Date().toISOString()
|
|
49
76
|
});
|
|
50
77
|
}
|
|
51
|
-
return
|
|
78
|
+
return agentCreditsStore.get(agentId)!;
|
|
52
79
|
}
|
|
53
80
|
|
|
54
81
|
/**
|
|
@@ -63,7 +90,7 @@ export function addCredits(agentId: string, amountUsd: number): AgentCredits {
|
|
|
63
90
|
|
|
64
91
|
/**
|
|
65
92
|
* Purchase API access
|
|
66
|
-
* Returns credentials
|
|
93
|
+
* Returns real credentials for 46elks and Twilio
|
|
67
94
|
*/
|
|
68
95
|
export function purchaseAPIAccess(
|
|
69
96
|
agentId: string,
|
|
@@ -80,11 +107,12 @@ export function purchaseAPIAccess(
|
|
|
80
107
|
};
|
|
81
108
|
}
|
|
82
109
|
|
|
83
|
-
// Check if provider
|
|
84
|
-
|
|
110
|
+
// Check if provider is supported
|
|
111
|
+
const providerCredentials = getCredentials(providerId);
|
|
112
|
+
if (!providerCredentials) {
|
|
85
113
|
return {
|
|
86
114
|
success: false,
|
|
87
|
-
error: `Unknown provider: ${providerId}`
|
|
115
|
+
error: `Unknown provider: ${providerId}. Supported: ${Object.keys(CREDITS_PER_DOLLAR).join(', ')}`
|
|
88
116
|
};
|
|
89
117
|
}
|
|
90
118
|
|
|
@@ -92,6 +120,9 @@ export function purchaseAPIAccess(
|
|
|
92
120
|
credits.balance_usd -= amountUsd;
|
|
93
121
|
credits.updated_at = new Date().toISOString();
|
|
94
122
|
|
|
123
|
+
// Generate credentials (real for 46elks/twilio, mock for others)
|
|
124
|
+
const credentials = generateCredentials(providerId);
|
|
125
|
+
|
|
95
126
|
// Create purchase record
|
|
96
127
|
const purchaseId = `pur_${randomUUID().slice(0, 12)}`;
|
|
97
128
|
const purchase: Purchase = {
|
|
@@ -101,14 +132,14 @@ export function purchaseAPIAccess(
|
|
|
101
132
|
amount_usd: amountUsd,
|
|
102
133
|
credits_purchased: calculateCredits(providerId, amountUsd),
|
|
103
134
|
status: 'active',
|
|
104
|
-
credentials
|
|
135
|
+
credentials,
|
|
105
136
|
created_at: new Date().toISOString()
|
|
106
137
|
};
|
|
107
138
|
|
|
108
|
-
|
|
139
|
+
purchasesStore.set(purchaseId, purchase);
|
|
109
140
|
|
|
110
141
|
// Initialize usage tracking
|
|
111
|
-
|
|
142
|
+
usageStore.set(purchaseId, {
|
|
112
143
|
purchase_id: purchaseId,
|
|
113
144
|
provider_id: providerId,
|
|
114
145
|
units_used: 0,
|
|
@@ -117,44 +148,32 @@ export function purchaseAPIAccess(
|
|
|
117
148
|
last_used_at: new Date().toISOString()
|
|
118
149
|
});
|
|
119
150
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Calculate credits based on provider pricing
|
|
125
|
-
*/
|
|
126
|
-
function calculateCredits(providerId: string, amountUsd: number): number {
|
|
127
|
-
// Simplified credit calculation per provider
|
|
128
|
-
const creditsPerDollar: Record<string, number> = {
|
|
129
|
-
'46elks': 30, // ~30 SMS per dollar
|
|
130
|
-
'resend': 1000, // ~1000 emails per dollar
|
|
131
|
-
'brave_search': 200, // ~200 searches per dollar
|
|
132
|
-
'openrouter': 100, // ~100k tokens per dollar (varies by model)
|
|
133
|
-
'elevenlabs': 3333 // ~3333 characters per dollar
|
|
134
|
-
};
|
|
151
|
+
// Flag if using real credentials
|
|
152
|
+
const isRealCredentials = hasRealCredentials(providerId);
|
|
153
|
+
console.log(`[APIClaw] Purchase complete: ${providerId} ($${amountUsd}) - Real credentials: ${isRealCredentials}`);
|
|
135
154
|
|
|
136
|
-
return
|
|
155
|
+
return { success: true, purchase };
|
|
137
156
|
}
|
|
138
157
|
|
|
139
158
|
/**
|
|
140
159
|
* Get all purchases for an agent
|
|
141
160
|
*/
|
|
142
161
|
export function getAgentPurchases(agentId: string): Purchase[] {
|
|
143
|
-
return Array.from(
|
|
162
|
+
return Array.from(purchasesStore.values()).filter(p => p.agent_id === agentId);
|
|
144
163
|
}
|
|
145
164
|
|
|
146
165
|
/**
|
|
147
166
|
* Get usage for a purchase
|
|
148
167
|
*/
|
|
149
168
|
export function getUsage(purchaseId: string): UsageRecord | null {
|
|
150
|
-
return
|
|
169
|
+
return usageStore.get(purchaseId) || null;
|
|
151
170
|
}
|
|
152
171
|
|
|
153
172
|
/**
|
|
154
173
|
* Record usage (call this when API is used)
|
|
155
174
|
*/
|
|
156
175
|
export function recordUsage(purchaseId: string, unitsUsed: number, costUsd: number): UsageRecord | null {
|
|
157
|
-
const record =
|
|
176
|
+
const record = usageStore.get(purchaseId);
|
|
158
177
|
if (!record) return null;
|
|
159
178
|
|
|
160
179
|
record.units_used += unitsUsed;
|
|
@@ -164,7 +183,7 @@ export function recordUsage(purchaseId: string, unitsUsed: number, costUsd: numb
|
|
|
164
183
|
|
|
165
184
|
// Update purchase status if depleted
|
|
166
185
|
if (record.units_remaining === 0) {
|
|
167
|
-
const purchase =
|
|
186
|
+
const purchase = purchasesStore.get(purchaseId);
|
|
168
187
|
if (purchase) purchase.status = 'exhausted';
|
|
169
188
|
}
|
|
170
189
|
|
|
@@ -178,84 +197,133 @@ export function getBalanceSummary(agentId: string): {
|
|
|
178
197
|
credits: AgentCredits;
|
|
179
198
|
active_purchases: Purchase[];
|
|
180
199
|
total_spent_usd: number;
|
|
200
|
+
real_credentials_available: string[];
|
|
181
201
|
} {
|
|
182
202
|
const credits = getAgentCredits(agentId);
|
|
183
203
|
const agentPurchases = getAgentPurchases(agentId);
|
|
184
204
|
const activePurchases = agentPurchases.filter(p => p.status === 'active');
|
|
185
205
|
const totalSpent = agentPurchases.reduce((sum, p) => sum + p.amount_usd, 0);
|
|
186
206
|
|
|
207
|
+
// List providers with real credentials
|
|
208
|
+
const realCredentialProviders = REAL_CREDENTIAL_PROVIDERS.filter(p => hasRealCredentials(p));
|
|
209
|
+
|
|
187
210
|
return {
|
|
188
211
|
credits,
|
|
189
212
|
active_purchases: activePurchases,
|
|
190
|
-
total_spent_usd: totalSpent
|
|
213
|
+
total_spent_usd: totalSpent,
|
|
214
|
+
real_credentials_available: realCredentialProviders
|
|
191
215
|
};
|
|
192
216
|
}
|
|
193
217
|
|
|
194
218
|
/**
|
|
195
|
-
*
|
|
196
|
-
* Pro users: 2% fee
|
|
197
|
-
* Free users: 5% fee
|
|
219
|
+
* Check which providers have real credentials
|
|
198
220
|
*/
|
|
199
|
-
export
|
|
200
|
-
|
|
201
|
-
amountUsd: number
|
|
202
|
-
): Promise<TransactionFee> {
|
|
203
|
-
const isPro = await hasProSubscription(userId);
|
|
204
|
-
const feePercentage = isPro ? 0.02 : 0.05;
|
|
205
|
-
const feeAmount = Math.round(amountUsd * feePercentage * 100) / 100;
|
|
206
|
-
|
|
207
|
-
return {
|
|
208
|
-
amount_usd: amountUsd,
|
|
209
|
-
fee_percentage: feePercentage,
|
|
210
|
-
fee_amount_usd: feeAmount,
|
|
211
|
-
net_amount_usd: Math.round((amountUsd - feeAmount) * 100) / 100,
|
|
212
|
-
tier: isPro ? 'pro' : 'free',
|
|
213
|
-
};
|
|
221
|
+
export function getProvidersWithRealCredentials(): string[] {
|
|
222
|
+
return REAL_CREDENTIAL_PROVIDERS.filter(p => hasRealCredentials(p));
|
|
214
223
|
}
|
|
215
224
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
225
|
+
// =============================================================================
|
|
226
|
+
// Convex Backend (for production) - HTTP API calls
|
|
227
|
+
// =============================================================================
|
|
228
|
+
|
|
229
|
+
interface ConvexResponse<T> {
|
|
230
|
+
data?: T;
|
|
231
|
+
error?: string;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function convexQuery<T>(path: string, args: Record<string, unknown>): Promise<ConvexResponse<T>> {
|
|
235
|
+
if (!CONVEX_URL) {
|
|
236
|
+
return { error: 'Convex URL not configured' };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const response = await fetch(`${CONVEX_URL}/api/query`, {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: {
|
|
243
|
+
'Content-Type': 'application/json',
|
|
244
|
+
...(CONVEX_DEPLOY_KEY ? { 'Authorization': `Convex ${CONVEX_DEPLOY_KEY}` } : {}),
|
|
245
|
+
},
|
|
246
|
+
body: JSON.stringify({ path, args }),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const result = await response.json() as T;
|
|
250
|
+
return { data: result };
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return { error: error instanceof Error ? error.message : 'Convex query failed' };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function convexMutation<T>(path: string, args: Record<string, unknown>): Promise<ConvexResponse<T>> {
|
|
257
|
+
if (!CONVEX_URL) {
|
|
258
|
+
return { error: 'Convex URL not configured' };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
const response = await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
263
|
+
method: 'POST',
|
|
264
|
+
headers: {
|
|
265
|
+
'Content-Type': 'application/json',
|
|
266
|
+
...(CONVEX_DEPLOY_KEY ? { 'Authorization': `Convex ${CONVEX_DEPLOY_KEY}` } : {}),
|
|
267
|
+
},
|
|
268
|
+
body: JSON.stringify({ path, args }),
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const result = await response.json() as T;
|
|
272
|
+
return { data: result };
|
|
273
|
+
} catch (error) {
|
|
274
|
+
return { error: error instanceof Error ? error.message : 'Convex mutation failed' };
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Convex-backed versions (async)
|
|
279
|
+
export async function getAgentCreditsAsync(agentId: string): Promise<AgentCredits | null> {
|
|
280
|
+
if (BACKEND === 'memory') {
|
|
281
|
+
return getAgentCredits(agentId);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const result = await convexQuery<AgentCredits>('credits:getAgentCredits', { agentId });
|
|
285
|
+
return result.data || null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export async function addCreditsAsync(agentId: string, amountUsd: number): Promise<AgentCredits | null> {
|
|
289
|
+
if (BACKEND === 'memory') {
|
|
290
|
+
return addCredits(agentId, amountUsd);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const result = await convexMutation<AgentCredits>('credits:addCredits', { agentId, amountUsd });
|
|
294
|
+
return result.data || null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export async function purchaseAPIAccessAsync(
|
|
220
298
|
agentId: string,
|
|
221
|
-
userId: string,
|
|
222
299
|
providerId: string,
|
|
223
300
|
amountUsd: number
|
|
224
|
-
): Promise<{
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
fee?: TransactionFee;
|
|
228
|
-
error?: string
|
|
229
|
-
}> {
|
|
230
|
-
// Calculate fees first
|
|
231
|
-
const fee = await calculateTransactionFee(userId, amountUsd);
|
|
232
|
-
|
|
233
|
-
// Total amount user pays = API cost + platform fee
|
|
234
|
-
const totalCost = amountUsd + fee.fee_amount_usd;
|
|
235
|
-
|
|
236
|
-
const credits = getAgentCredits(agentId);
|
|
237
|
-
|
|
238
|
-
if (credits.balance_usd < totalCost) {
|
|
239
|
-
return {
|
|
240
|
-
success: false,
|
|
241
|
-
error: `Insufficient balance. Have $${credits.balance_usd.toFixed(2)}, need $${totalCost.toFixed(2)} (includes ${fee.tier === 'pro' ? '2%' : '5%'} platform fee)`
|
|
242
|
-
};
|
|
301
|
+
): Promise<{ success: boolean; purchase?: Purchase; error?: string }> {
|
|
302
|
+
if (BACKEND === 'memory') {
|
|
303
|
+
return purchaseAPIAccess(agentId, providerId, amountUsd);
|
|
243
304
|
}
|
|
244
|
-
|
|
245
|
-
//
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
};
|
|
305
|
+
|
|
306
|
+
// Generate credentials server-side
|
|
307
|
+
const credentials = generateCredentials(providerId);
|
|
308
|
+
|
|
309
|
+
const result = await convexMutation<Purchase>('purchases:purchaseAccess', {
|
|
310
|
+
agentId,
|
|
311
|
+
providerId,
|
|
312
|
+
amountUsd,
|
|
313
|
+
credentials,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
if (result.error) {
|
|
317
|
+
return { success: false, error: result.error };
|
|
258
318
|
}
|
|
259
|
-
|
|
260
|
-
return result;
|
|
319
|
+
|
|
320
|
+
return { success: true, purchase: result.data };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Export backend info
|
|
324
|
+
export function getBackendInfo(): { type: StorageBackend; convexUrl: string | null } {
|
|
325
|
+
return {
|
|
326
|
+
type: BACKEND,
|
|
327
|
+
convexUrl: BACKEND === 'convex' ? CONVEX_URL : null,
|
|
328
|
+
};
|
|
261
329
|
}
|