@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
package/STATUS.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# APIClaw Status — Verifierad 21 Feb 2026, 17:15 CET
|
|
2
|
+
|
|
3
|
+
> **Denna fil är source of truth.** Uppdatera vid varje deploy/ändring.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📊 Snabbstatus
|
|
8
|
+
|
|
9
|
+
| Komponent | Status | Detaljer |
|
|
10
|
+
|-----------|--------|----------|
|
|
11
|
+
| **npm** | ✅ Live | `@nordsym/apiclaw@1.0.0` (publicerad 16 feb) |
|
|
12
|
+
| **Landing** | ✅ Live | https://apiclaw.nordsym.com |
|
|
13
|
+
| **Convex** | ✅ Deployat | `brilliant-puffin-712` |
|
|
14
|
+
| **API Registry** | ✅ 10,001 | Version 3.0.0, uppdaterad 21 feb |
|
|
15
|
+
| **Kategorier** | ✅ 572 | Auto-extraherade |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 📦 npm Package
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Package: @nordsym/apiclaw
|
|
23
|
+
Published: v1.0.0 (16 feb 2026)
|
|
24
|
+
Local: v0.3.0 (package.json)
|
|
25
|
+
Registry: https://registry.npmjs.org/@nordsym/apiclaw
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**⚠️ Notera:** Lokal version (0.3.0) != publicerad (1.0.0). Vid nästa publish, bumpa till 1.1.0.
|
|
29
|
+
|
|
30
|
+
**Installation:**
|
|
31
|
+
```bash
|
|
32
|
+
npx @nordsym/apiclaw # Kör direkt
|
|
33
|
+
npm install @nordsym/apiclaw # Installera
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 🌐 Landing Page
|
|
39
|
+
|
|
40
|
+
| | |
|
|
41
|
+
|-|-|
|
|
42
|
+
| **URL** | https://apiclaw.nordsym.com |
|
|
43
|
+
| **Hosting** | Vercel |
|
|
44
|
+
| **Project** | `landing` (prj_PmUzn4YRoL3IIBcPai2TLuIFuScE) |
|
|
45
|
+
| **Status** | ✅ HTTP 200 |
|
|
46
|
+
|
|
47
|
+
**Deploy:**
|
|
48
|
+
```bash
|
|
49
|
+
cd ~/Projects/apiclaw/landing
|
|
50
|
+
npx vercel --prod
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 🗄️ Convex Backend
|
|
56
|
+
|
|
57
|
+
| | |
|
|
58
|
+
|-|-|
|
|
59
|
+
| **Deployment** | `brilliant-puffin-712` |
|
|
60
|
+
| **Dashboard** | https://dashboard.convex.dev/d/brilliant-puffin-712 |
|
|
61
|
+
| **Status** | ✅ Deployat |
|
|
62
|
+
|
|
63
|
+
**Tabeller:**
|
|
64
|
+
- `providers` — API-providers (0 docs)
|
|
65
|
+
- `providerAPIs` — Provider-listade API:er
|
|
66
|
+
- `apis` — Fullt managerade API:er
|
|
67
|
+
- `agentCredits` — Agent credit-balanser (0 docs)
|
|
68
|
+
- `purchases` — Köphistorik
|
|
69
|
+
- `apiCalls` — Användningsloggar
|
|
70
|
+
- `creditTopups` — Credit-påfyllningar
|
|
71
|
+
- `sessions` — Provider-sessioner
|
|
72
|
+
- `magicLinks` — Email-auth tokens
|
|
73
|
+
- `payouts` — Provider-utbetalningar
|
|
74
|
+
|
|
75
|
+
**⚠️ Env vars:** Inga satta på prod. Behövs för server-side Instant Connect.
|
|
76
|
+
|
|
77
|
+
**Deploy:**
|
|
78
|
+
```bash
|
|
79
|
+
cd ~/Projects/apiclaw
|
|
80
|
+
npx convex deploy
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 🔍 API Registry
|
|
86
|
+
|
|
87
|
+
| | |
|
|
88
|
+
|-|-|
|
|
89
|
+
| **Fil** | `src/registry/apis.json` |
|
|
90
|
+
| **Antal API:er** | 10,001 |
|
|
91
|
+
| **Kategorier** | 572 |
|
|
92
|
+
| **Version** | 3.0.0 |
|
|
93
|
+
| **Senast uppdaterad** | 2026-02-21 |
|
|
94
|
+
|
|
95
|
+
**Struktur:**
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"version": "3.0.0",
|
|
99
|
+
"source": "apis.guru + manual",
|
|
100
|
+
"lastUpdated": "2026-02-21",
|
|
101
|
+
"count": 7692,
|
|
102
|
+
"apis": [...]
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## ⚡ Instant Connect
|
|
109
|
+
|
|
110
|
+
**6 providers konfigurerade lokalt:**
|
|
111
|
+
|
|
112
|
+
| Provider | Action | Credentials | Status |
|
|
113
|
+
|----------|--------|-------------|--------|
|
|
114
|
+
| `46elks` | `send_sms` | `~/.secrets/46elks.env` | ✅ Lokal |
|
|
115
|
+
| `twilio` | `send_sms` | `~/.secrets/twilio.env` | ✅ Lokal |
|
|
116
|
+
| `brave_search` | `search` | `~/.secrets/brave.env` | ✅ Lokal |
|
|
117
|
+
| `resend` | `send_email` | `~/.secrets/resend.env` | ✅ Lokal |
|
|
118
|
+
| `openrouter` | `chat` | `~/.secrets/openrouter.env` | ✅ Lokal |
|
|
119
|
+
| `elevenlabs` | `text_to_speech` | `~/.secrets/elevenlabs.env` | ✅ Lokal |
|
|
120
|
+
|
|
121
|
+
**Kod:** `src/execute.ts`
|
|
122
|
+
|
|
123
|
+
**⚠️ Cloud-status:** Fungerar lokalt (läser env-filer). För cloud/hosted MCP behöver credentials lagras i Convex eller env vars.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 🔧 MCP Tools (8 st)
|
|
128
|
+
|
|
129
|
+
| Tool | Beskrivning | Status |
|
|
130
|
+
|------|-------------|--------|
|
|
131
|
+
| `discover_apis` | Sök API:er efter capability | ✅ |
|
|
132
|
+
| `get_api_details` | Hämta full API-info | ✅ |
|
|
133
|
+
| `list_categories` | Lista alla kategorier | ✅ |
|
|
134
|
+
| `list_connected` | Visa Instant Connect-providers | ✅ |
|
|
135
|
+
| `call_api` | Kör API via Instant Connect | ✅ |
|
|
136
|
+
| `purchase_access` | Köp API-access | ✅ |
|
|
137
|
+
| `check_balance` | Kolla credits | ✅ |
|
|
138
|
+
| `add_credits` | Lägg till test-credits | ✅ |
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 📁 Projektstruktur
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
apiclaw/
|
|
146
|
+
├── src/ # MCP Server (TypeScript)
|
|
147
|
+
│ ├── index.ts # Huvudfil, MCP tool definitions
|
|
148
|
+
│ ├── discovery.ts # Söklogik
|
|
149
|
+
│ ├── execute.ts # Instant Connect handlers
|
|
150
|
+
│ ├── credentials.ts # Credential-hantering
|
|
151
|
+
│ ├── credits.ts # Credit-system
|
|
152
|
+
│ ├── stripe.ts # Stripe (förberett)
|
|
153
|
+
│ └── registry/
|
|
154
|
+
│ └── apis.json # 7,692 API:er
|
|
155
|
+
│
|
|
156
|
+
├── landing/ # Next.js frontend
|
|
157
|
+
│ └── src/app/
|
|
158
|
+
│ ├── page.tsx # Homepage
|
|
159
|
+
│ ├── providers/ # Provider-portal
|
|
160
|
+
│ └── admin/ # Admin-panel
|
|
161
|
+
│
|
|
162
|
+
├── convex/ # Backend
|
|
163
|
+
│ ├── schema.ts # Databasschema
|
|
164
|
+
│ ├── providers.ts # Provider CRUD
|
|
165
|
+
│ └── credits.ts # Credits
|
|
166
|
+
│
|
|
167
|
+
├── dist/ # Kompilerad JS
|
|
168
|
+
├── package.json # v0.3.0 (lokal)
|
|
169
|
+
├── STATUS.md # DENNA FIL
|
|
170
|
+
└── README.md # Dokumentation
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 🚀 Deploy Checklist
|
|
176
|
+
|
|
177
|
+
### Full deploy:
|
|
178
|
+
```bash
|
|
179
|
+
cd ~/Projects/apiclaw
|
|
180
|
+
|
|
181
|
+
# 1. Bygg
|
|
182
|
+
npm run build
|
|
183
|
+
|
|
184
|
+
# 2. Synka registry till landing
|
|
185
|
+
cp src/registry/apis.json landing/src/lib/
|
|
186
|
+
cd landing && node scripts/generate-stats.js && cd ..
|
|
187
|
+
|
|
188
|
+
# 3. Deploy Convex
|
|
189
|
+
npx convex deploy
|
|
190
|
+
|
|
191
|
+
# 4. Deploy Landing
|
|
192
|
+
cd landing && npx vercel --prod && cd ..
|
|
193
|
+
|
|
194
|
+
# 5. Publish npm (om ny version)
|
|
195
|
+
npm publish --access public
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Snabb landing-deploy:
|
|
199
|
+
```bash
|
|
200
|
+
cd ~/Projects/apiclaw
|
|
201
|
+
bash scripts/sync-and-deploy.sh
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## ❌ Vad som INTE är klart
|
|
207
|
+
|
|
208
|
+
| Feature | Prioritet | Blocker |
|
|
209
|
+
|---------|-----------|---------|
|
|
210
|
+
| Stripe payments live | Medium | Behöver webhook setup |
|
|
211
|
+
| Provider dashboard med live data | Medium | Ingen data i Convex |
|
|
212
|
+
| Cloud Instant Connect | Medium | Credentials ej i cloud |
|
|
213
|
+
| Rate limiting | Låg | — |
|
|
214
|
+
| Usage metering | Låg | Schema finns |
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## 📈 Nästa steg (prioriterat)
|
|
219
|
+
|
|
220
|
+
1. **Öka API-antal** — Scrapa fler källor för 10k+
|
|
221
|
+
2. **Testa Instant Connect E2E** — Verifiera alla 6 providers
|
|
222
|
+
3. **Stripe wiring** — Koppla betalflöde
|
|
223
|
+
4. **Launch prep** — PH, X thread, etc.
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## 📝 Ändringslogg
|
|
228
|
+
|
|
229
|
+
| Datum | Ändring |
|
|
230
|
+
|-------|---------|
|
|
231
|
+
| 2026-02-21 | STATUS.md skapad, verifierad mot prod |
|
|
232
|
+
| 2026-02-16 | npm v1.0.0 publicerad |
|
|
233
|
+
| 2026-02-21 | Registry uppdaterat till 7,692 API:er |
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
*Senast verifierad: 21 Feb 2026, 17:15 CET*
|
package/TOOLS.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# TOOLS.md - Local Notes
|
|
2
|
+
|
|
3
|
+
Skills define *how* tools work. This file is for *your* specifics — the stuff that's unique to your setup.
|
|
4
|
+
|
|
5
|
+
## What Goes Here
|
|
6
|
+
|
|
7
|
+
Things like:
|
|
8
|
+
- Camera names and locations
|
|
9
|
+
- SSH hosts and aliases
|
|
10
|
+
- Preferred voices for TTS
|
|
11
|
+
- Speaker/room names
|
|
12
|
+
- Device nicknames
|
|
13
|
+
- Anything environment-specific
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
|
|
17
|
+
```markdown
|
|
18
|
+
### Cameras
|
|
19
|
+
- living-room → Main area, 180° wide angle
|
|
20
|
+
- front-door → Entrance, motion-triggered
|
|
21
|
+
|
|
22
|
+
### SSH
|
|
23
|
+
- home-server → 192.168.1.100, user: admin
|
|
24
|
+
|
|
25
|
+
### TTS
|
|
26
|
+
- Preferred voice: "Nova" (warm, slightly British)
|
|
27
|
+
- Default speaker: Kitchen HomePod
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Why Separate?
|
|
31
|
+
|
|
32
|
+
Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
Add whatever helps you do your job. This is your cheat sheet.
|
package/USER.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# USER.md - About Your Human
|
|
2
|
+
|
|
3
|
+
*Learn about the person you're helping. Update this as you go.*
|
|
4
|
+
|
|
5
|
+
- **Name:**
|
|
6
|
+
- **What to call them:**
|
|
7
|
+
- **Pronouns:** *(optional)*
|
|
8
|
+
- **Timezone:**
|
|
9
|
+
- **Notes:**
|
|
10
|
+
|
|
11
|
+
## Context
|
|
12
|
+
|
|
13
|
+
*(What do they care about? What projects are they working on? What annoys them? What makes them laugh? Build this over time.)*
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
The more you know, the better you can help. But remember — you're learning about a person, not building a dossier. Respect the difference.
|
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type * as
|
|
11
|
+
import type * as analytics from "../analytics.js";
|
|
12
|
+
import type * as credits from "../credits.js";
|
|
13
|
+
import type * as http from "../http.js";
|
|
14
|
+
import type * as providers from "../providers.js";
|
|
12
15
|
import type * as purchases from "../purchases.js";
|
|
13
|
-
import type * as
|
|
14
|
-
import type * as
|
|
16
|
+
import type * as ratelimit from "../ratelimit.js";
|
|
17
|
+
import type * as telemetry from "../telemetry.js";
|
|
15
18
|
|
|
16
19
|
import type {
|
|
17
20
|
ApiFromModules,
|
|
@@ -20,10 +23,13 @@ import type {
|
|
|
20
23
|
} from "convex/server";
|
|
21
24
|
|
|
22
25
|
declare const fullApi: ApiFromModules<{
|
|
23
|
-
|
|
26
|
+
analytics: typeof analytics;
|
|
27
|
+
credits: typeof credits;
|
|
28
|
+
http: typeof http;
|
|
29
|
+
providers: typeof providers;
|
|
24
30
|
purchases: typeof purchases;
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
ratelimit: typeof ratelimit;
|
|
32
|
+
telemetry: typeof telemetry;
|
|
27
33
|
}>;
|
|
28
34
|
|
|
29
35
|
/**
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { mutation, query } from "./_generated/server";
|
|
2
|
+
import { v } from "convex/values";
|
|
3
|
+
|
|
4
|
+
// Log an analytics event
|
|
5
|
+
export const log = mutation({
|
|
6
|
+
args: {
|
|
7
|
+
event: v.string(),
|
|
8
|
+
provider: v.optional(v.string()),
|
|
9
|
+
query: v.optional(v.string()),
|
|
10
|
+
identifier: v.string(),
|
|
11
|
+
metadata: v.optional(v.any()),
|
|
12
|
+
},
|
|
13
|
+
handler: async (ctx, args) => {
|
|
14
|
+
return await ctx.db.insert("analytics", {
|
|
15
|
+
...args,
|
|
16
|
+
timestamp: Date.now(),
|
|
17
|
+
});
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Get stats for dashboard
|
|
22
|
+
export const getStats = query({
|
|
23
|
+
args: {
|
|
24
|
+
hoursBack: v.optional(v.number()),
|
|
25
|
+
},
|
|
26
|
+
handler: async (ctx, args) => {
|
|
27
|
+
const hoursBack = args.hoursBack || 24;
|
|
28
|
+
const since = Date.now() - hoursBack * 3600000;
|
|
29
|
+
|
|
30
|
+
const events = await ctx.db
|
|
31
|
+
.query("analytics")
|
|
32
|
+
.withIndex("by_timestamp")
|
|
33
|
+
.filter((q) => q.gte(q.field("timestamp"), since))
|
|
34
|
+
.collect();
|
|
35
|
+
|
|
36
|
+
// Aggregate stats
|
|
37
|
+
const stats = {
|
|
38
|
+
totalEvents: events.length,
|
|
39
|
+
discoveries: events.filter((e) => e.event === "discovery").length,
|
|
40
|
+
instantCalls: events.filter((e) => e.event === "instant").length,
|
|
41
|
+
uniqueUsers: new Set(events.map((e) => e.identifier)).size,
|
|
42
|
+
byProvider: {} as Record<string, number>,
|
|
43
|
+
topQueries: [] as { query: string; count: number }[],
|
|
44
|
+
hourly: [] as { hour: string; count: number }[],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// By provider
|
|
48
|
+
for (const event of events.filter((e) => e.provider)) {
|
|
49
|
+
stats.byProvider[event.provider!] = (stats.byProvider[event.provider!] || 0) + 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Top queries
|
|
53
|
+
const queryCounts: Record<string, number> = {};
|
|
54
|
+
for (const event of events.filter((e) => e.query)) {
|
|
55
|
+
queryCounts[event.query!] = (queryCounts[event.query!] || 0) + 1;
|
|
56
|
+
}
|
|
57
|
+
stats.topQueries = Object.entries(queryCounts)
|
|
58
|
+
.sort(([, a], [, b]) => b - a)
|
|
59
|
+
.slice(0, 10)
|
|
60
|
+
.map(([query, count]) => ({ query, count }));
|
|
61
|
+
|
|
62
|
+
// Hourly breakdown
|
|
63
|
+
const hourlyCounts: Record<string, number> = {};
|
|
64
|
+
for (const event of events) {
|
|
65
|
+
const hour = new Date(event.timestamp).toISOString().slice(0, 13);
|
|
66
|
+
hourlyCounts[hour] = (hourlyCounts[hour] || 0) + 1;
|
|
67
|
+
}
|
|
68
|
+
stats.hourly = Object.entries(hourlyCounts)
|
|
69
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
70
|
+
.map(([hour, count]) => ({ hour, count }));
|
|
71
|
+
|
|
72
|
+
return stats;
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Get recent events for live feed
|
|
77
|
+
export const getRecent = query({
|
|
78
|
+
args: {
|
|
79
|
+
limit: v.optional(v.number()),
|
|
80
|
+
},
|
|
81
|
+
handler: async (ctx, args) => {
|
|
82
|
+
const limit = args.limit || 50;
|
|
83
|
+
|
|
84
|
+
return await ctx.db
|
|
85
|
+
.query("analytics")
|
|
86
|
+
.withIndex("by_timestamp")
|
|
87
|
+
.order("desc")
|
|
88
|
+
.take(limit);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query } from "./_generated/server";
|
|
3
|
+
|
|
4
|
+
// Credit packages
|
|
5
|
+
export const CREDIT_PACKAGES = {
|
|
6
|
+
starter: { amountUsd: 10, credits: 100, bonus: 0 },
|
|
7
|
+
growth: { amountUsd: 50, credits: 550, bonus: 50 }, // 10% bonus
|
|
8
|
+
scale: { amountUsd: 100, credits: 1200, bonus: 200 }, // 20% bonus
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
// Get or create agent credits account
|
|
12
|
+
export const getOrCreateAgent = mutation({
|
|
13
|
+
args: { agentId: v.string() },
|
|
14
|
+
handler: async (ctx, args) => {
|
|
15
|
+
const existing = await ctx.db
|
|
16
|
+
.query("agentCredits")
|
|
17
|
+
.withIndex("by_agentId", (q) => q.eq("agentId", args.agentId))
|
|
18
|
+
.first();
|
|
19
|
+
|
|
20
|
+
if (existing) return existing;
|
|
21
|
+
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const id = await ctx.db.insert("agentCredits", {
|
|
24
|
+
agentId: args.agentId,
|
|
25
|
+
balanceUsd: 0,
|
|
26
|
+
currency: "USD",
|
|
27
|
+
createdAt: now,
|
|
28
|
+
updatedAt: now,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
return await ctx.db.get(id);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Get agent credits
|
|
36
|
+
export const getAgentCredits = query({
|
|
37
|
+
args: { agentId: v.string() },
|
|
38
|
+
handler: async (ctx, args) => {
|
|
39
|
+
return await ctx.db
|
|
40
|
+
.query("agentCredits")
|
|
41
|
+
.withIndex("by_agentId", (q) => q.eq("agentId", args.agentId))
|
|
42
|
+
.first();
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Add credits to agent account (called by webhook or admin)
|
|
47
|
+
export const addCredits = mutation({
|
|
48
|
+
args: {
|
|
49
|
+
agentId: v.string(),
|
|
50
|
+
amountUsd: v.number(),
|
|
51
|
+
source: v.optional(v.string()),
|
|
52
|
+
},
|
|
53
|
+
handler: async (ctx, args) => {
|
|
54
|
+
const credits = await ctx.db
|
|
55
|
+
.query("agentCredits")
|
|
56
|
+
.withIndex("by_agentId", (q) => q.eq("agentId", args.agentId))
|
|
57
|
+
.first();
|
|
58
|
+
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
|
|
61
|
+
if (credits) {
|
|
62
|
+
await ctx.db.patch(credits._id, {
|
|
63
|
+
balanceUsd: credits.balanceUsd + args.amountUsd,
|
|
64
|
+
updatedAt: now,
|
|
65
|
+
});
|
|
66
|
+
return await ctx.db.get(credits._id);
|
|
67
|
+
} else {
|
|
68
|
+
const id = await ctx.db.insert("agentCredits", {
|
|
69
|
+
agentId: args.agentId,
|
|
70
|
+
balanceUsd: args.amountUsd,
|
|
71
|
+
currency: "USD",
|
|
72
|
+
createdAt: now,
|
|
73
|
+
updatedAt: now,
|
|
74
|
+
});
|
|
75
|
+
return await ctx.db.get(id);
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Deduct credits (internal use)
|
|
81
|
+
export const deductCredits = mutation({
|
|
82
|
+
args: {
|
|
83
|
+
agentId: v.string(),
|
|
84
|
+
amountUsd: v.number(),
|
|
85
|
+
},
|
|
86
|
+
handler: async (ctx, args) => {
|
|
87
|
+
const credits = await ctx.db
|
|
88
|
+
.query("agentCredits")
|
|
89
|
+
.withIndex("by_agentId", (q) => q.eq("agentId", args.agentId))
|
|
90
|
+
.first();
|
|
91
|
+
|
|
92
|
+
if (!credits) {
|
|
93
|
+
throw new Error(`No credits account for agent: ${args.agentId}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (credits.balanceUsd < args.amountUsd) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Insufficient balance: have $${credits.balanceUsd.toFixed(2)}, need $${args.amountUsd.toFixed(2)}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await ctx.db.patch(credits._id, {
|
|
103
|
+
balanceUsd: credits.balanceUsd - args.amountUsd,
|
|
104
|
+
updatedAt: Date.now(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return await ctx.db.get(credits._id);
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Record credit top-up from Stripe
|
|
112
|
+
export const recordTopup = mutation({
|
|
113
|
+
args: {
|
|
114
|
+
agentId: v.string(),
|
|
115
|
+
stripeSessionId: v.optional(v.string()),
|
|
116
|
+
stripePaymentIntentId: v.optional(v.string()),
|
|
117
|
+
amountUsd: v.number(),
|
|
118
|
+
creditsGranted: v.number(),
|
|
119
|
+
packageType: v.string(),
|
|
120
|
+
status: v.string(),
|
|
121
|
+
},
|
|
122
|
+
handler: async (ctx, args) => {
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
return await ctx.db.insert("creditTopups", {
|
|
125
|
+
agentId: args.agentId,
|
|
126
|
+
stripeSessionId: args.stripeSessionId,
|
|
127
|
+
stripePaymentIntentId: args.stripePaymentIntentId,
|
|
128
|
+
amountUsd: args.amountUsd,
|
|
129
|
+
creditsGranted: args.creditsGranted,
|
|
130
|
+
packageType: args.packageType,
|
|
131
|
+
status: args.status,
|
|
132
|
+
createdAt: now,
|
|
133
|
+
completedAt: args.status === "completed" ? now : undefined,
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Complete a pending top-up
|
|
139
|
+
export const completeTopup = mutation({
|
|
140
|
+
args: {
|
|
141
|
+
stripeSessionId: v.optional(v.string()),
|
|
142
|
+
stripePaymentIntentId: v.optional(v.string()),
|
|
143
|
+
},
|
|
144
|
+
handler: async (ctx, args) => {
|
|
145
|
+
let topup;
|
|
146
|
+
|
|
147
|
+
if (args.stripeSessionId) {
|
|
148
|
+
topup = await ctx.db
|
|
149
|
+
.query("creditTopups")
|
|
150
|
+
.withIndex("by_stripeSessionId", (q) =>
|
|
151
|
+
q.eq("stripeSessionId", args.stripeSessionId)
|
|
152
|
+
)
|
|
153
|
+
.first();
|
|
154
|
+
} else if (args.stripePaymentIntentId) {
|
|
155
|
+
topup = await ctx.db
|
|
156
|
+
.query("creditTopups")
|
|
157
|
+
.withIndex("by_stripePaymentIntentId", (q) =>
|
|
158
|
+
q.eq("stripePaymentIntentId", args.stripePaymentIntentId)
|
|
159
|
+
)
|
|
160
|
+
.first();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!topup) {
|
|
164
|
+
throw new Error("Top-up not found");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (topup.status === "completed") {
|
|
168
|
+
return topup; // Already completed, idempotent
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Update top-up status
|
|
172
|
+
await ctx.db.patch(topup._id, {
|
|
173
|
+
status: "completed",
|
|
174
|
+
completedAt: Date.now(),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Add credits to agent
|
|
178
|
+
const credits = await ctx.db
|
|
179
|
+
.query("agentCredits")
|
|
180
|
+
.withIndex("by_agentId", (q) => q.eq("agentId", topup.agentId))
|
|
181
|
+
.first();
|
|
182
|
+
|
|
183
|
+
if (credits) {
|
|
184
|
+
await ctx.db.patch(credits._id, {
|
|
185
|
+
balanceUsd: credits.balanceUsd + topup.creditsGranted,
|
|
186
|
+
updatedAt: Date.now(),
|
|
187
|
+
});
|
|
188
|
+
} else {
|
|
189
|
+
await ctx.db.insert("agentCredits", {
|
|
190
|
+
agentId: topup.agentId,
|
|
191
|
+
balanceUsd: topup.creditsGranted,
|
|
192
|
+
currency: "USD",
|
|
193
|
+
createdAt: Date.now(),
|
|
194
|
+
updatedAt: Date.now(),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return topup;
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Get all top-ups for an agent
|
|
203
|
+
export const getTopups = query({
|
|
204
|
+
args: { agentId: v.string() },
|
|
205
|
+
handler: async (ctx, args) => {
|
|
206
|
+
return await ctx.db
|
|
207
|
+
.query("creditTopups")
|
|
208
|
+
.withIndex("by_agentId", (q) => q.eq("agentId", args.agentId))
|
|
209
|
+
.collect();
|
|
210
|
+
},
|
|
211
|
+
});
|