@nordsym/apiclaw 1.8.6 → 1.8.7
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 +4 -4
- package/apiclaw-README.md +12 -12
- package/convex/_generated/api.d.ts +4 -0
- package/convex/apiKeys.ts +220 -0
- package/convex/directCall.ts +49 -14
- package/convex/email.ts +5 -5
- package/convex/http.ts +598 -40
- package/convex/logs.ts +26 -22
- package/convex/migrateProviderWorkspaces.ts +154 -35
- package/convex/providers.ts +313 -203
- package/convex/schema.ts +50 -1
- package/convex/searchLogs.ts +2 -6
- package/convex/seedPratham.ts +1 -1
- package/convex/spendAlerts.ts +2 -2
- package/convex/stripeActions.ts +1 -1
- package/convex/updateAPIStatus.ts +2 -3
- package/convex/workspaceSettings.ts +136 -0
- package/dist/cli/commands/demo.js +2 -2
- package/dist/cli/commands/doctor.js +1 -1
- package/dist/cli/commands/login.js +1 -1
- package/dist/cli/commands/setup.js +2 -2
- package/dist/discovery.js +1 -1
- package/dist/execute.js +3 -3
- package/dist/index.js +13 -13
- package/dist/ui/errors.js +16 -16
- package/dist/ui/prompts.js +1 -1
- package/email-templates/filestack-provider-outreach.html +1 -1
- package/email-templates/partnership-template.html +1 -1
- package/email-templates/pratham-partnership-draft.html +2 -2
- package/package.json +2 -2
- package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
- package/reports/session-report-2026-04-05.html +433 -0
- package/src/cli/commands/demo.ts +2 -2
- package/src/cli/commands/doctor.ts +1 -1
- package/src/cli/commands/login.ts +1 -1
- package/src/cli/commands/setup.ts +2 -2
- package/src/discovery.ts +1 -1
- package/src/execute.ts +3 -3
- package/src/index.ts +14 -14
- package/src/ui/errors.ts +16 -16
- package/src/ui/prompts.ts +1 -1
- package/convex/adminActivate.d.ts +0 -3
- package/convex/adminActivate.js +0 -47
- package/convex/adminStats.d.ts +0 -9
- package/convex/adminStats.js +0 -280
- package/convex/agents.d.ts +0 -84
- package/convex/agents.js +0 -809
- package/convex/analytics.d.ts +0 -5
- package/convex/analytics.js +0 -167
- package/convex/backfillAnalytics.d.ts +0 -2
- package/convex/backfillAnalytics.js +0 -20
- package/convex/backfillSearchLogs.d.ts +0 -2
- package/convex/backfillSearchLogs.js +0 -29
- package/convex/billing.d.ts +0 -88
- package/convex/billing.js +0 -655
- package/convex/capabilities.d.ts +0 -9
- package/convex/capabilities.js +0 -145
- package/convex/chains.d.ts +0 -68
- package/convex/chains.js +0 -1105
- package/convex/credits.d.ts +0 -25
- package/convex/credits.js +0 -186
- package/convex/crons.d.ts +0 -3
- package/convex/crons.js +0 -17
- package/convex/debugFilestackLogs.d.ts +0 -2
- package/convex/debugFilestackLogs.js +0 -17
- package/convex/debugGetToken.d.ts +0 -2
- package/convex/debugGetToken.js +0 -18
- package/convex/directCall.d.ts +0 -72
- package/convex/directCall.js +0 -627
- package/convex/earnProgress.d.ts +0 -58
- package/convex/earnProgress.js +0 -649
- package/convex/email.d.ts +0 -14
- package/convex/email.js +0 -300
- package/convex/feedback.d.ts +0 -7
- package/convex/feedback.js +0 -227
- package/convex/http.d.ts +0 -3
- package/convex/http.js +0 -1408
- package/convex/inbound.d.ts +0 -2
- package/convex/inbound.js +0 -32
- package/convex/logs.d.ts +0 -48
- package/convex/logs.js +0 -621
- package/convex/migrateFilestack.d.ts +0 -2
- package/convex/migrateFilestack.js +0 -74
- package/convex/migratePartnersProd.d.ts +0 -8
- package/convex/migratePartnersProd.js +0 -165
- package/convex/migratePratham.d.ts +0 -2
- package/convex/migratePratham.js +0 -121
- package/convex/migrateProviderWorkspaces.d.ts +0 -2
- package/convex/migrateProviderWorkspaces.js +0 -55
- package/convex/mou.d.ts +0 -6
- package/convex/mou.js +0 -82
- package/convex/providerKeys.d.ts +0 -31
- package/convex/providerKeys.js +0 -257
- package/convex/providers.d.ts +0 -35
- package/convex/providers.js +0 -922
- package/convex/purchases.d.ts +0 -7
- package/convex/purchases.js +0 -157
- package/convex/ratelimit.d.ts +0 -4
- package/convex/ratelimit.js +0 -91
- package/convex/searchLogs.d.ts +0 -13
- package/convex/searchLogs.js +0 -246
- package/convex/seedAPILayerAPIs.d.ts +0 -7
- package/convex/seedAPILayerAPIs.js +0 -177
- package/convex/seedDirectCallConfigs.d.ts +0 -2
- package/convex/seedDirectCallConfigs.js +0 -324
- package/convex/seedPratham.d.ts +0 -6
- package/convex/seedPratham.js +0 -150
- package/convex/spendAlerts.d.ts +0 -36
- package/convex/spendAlerts.js +0 -380
- package/convex/stripeActions.d.ts +0 -19
- package/convex/stripeActions.js +0 -411
- package/convex/teams.d.ts +0 -21
- package/convex/teams.js +0 -215
- package/convex/telemetry.d.ts +0 -4
- package/convex/telemetry.js +0 -74
- package/convex/updateAPIStatus.d.ts +0 -6
- package/convex/updateAPIStatus.js +0 -40
- package/convex/usage.d.ts +0 -27
- package/convex/usage.js +0 -229
- package/convex/waitlist.d.ts +0 -4
- package/convex/waitlist.js +0 -49
- package/convex/webhooks.d.ts +0 -12
- package/convex/webhooks.js +0 -410
- package/convex/workspaces.d.ts +0 -33
- package/convex/workspaces.js +0 -991
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ The API layer for AI agents. One install. Three tiers of access.
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
curl -fsSL https://apiclaw.
|
|
14
|
+
curl -fsSL https://apiclaw.cloud/install.sh | bash
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Restart your AI assistant. First 5 API calls are free. Register your email to unlock 50/month.
|
|
@@ -84,16 +84,16 @@ Premium APIs proxied through APIClaw. No keys needed. APIClaw handles auth, rate
|
|
|
84
84
|
| Unregistered | Free | 5 API calls, unlimited search |
|
|
85
85
|
| Free | Free | 50 calls/month, full dashboard |
|
|
86
86
|
| Founding Backer | $199 one-time | Unlimited until 2027 |
|
|
87
|
-
| Enterprise | Custom | [Book a call](https://apiclaw.
|
|
87
|
+
| Enterprise | Custom | [Book a call](https://apiclaw.cloud/book) |
|
|
88
88
|
|
|
89
89
|
## For API Providers
|
|
90
90
|
|
|
91
91
|
List your APIs on APIClaw. Get discovered by AI agents. Track usage, discoveries, and calls in your dashboard.
|
|
92
92
|
|
|
93
|
-
[Register as provider](https://apiclaw.
|
|
93
|
+
[Register as provider](https://apiclaw.cloud/providers/register)
|
|
94
94
|
|
|
95
95
|
---
|
|
96
96
|
|
|
97
|
-
[Dashboard](https://apiclaw.
|
|
97
|
+
[Dashboard](https://apiclaw.cloud) - [Docs](https://apiclaw.cloud/docs) - [Book a Call](https://apiclaw.cloud/book)
|
|
98
98
|
|
|
99
99
|
MIT License
|
package/apiclaw-README.md
CHANGED
|
@@ -23,7 +23,7 @@ The API layer for AI agents. 18 providers → 1000s of capabilities. Workspace
|
|
|
23
23
|
|
|
24
24
|
## The Platform
|
|
25
25
|
|
|
26
|
-
**[apiclaw.
|
|
26
|
+
**[apiclaw.cloud](https://apiclaw.cloud)** — Your workspace for API-powered agents.
|
|
27
27
|
|
|
28
28
|
| Layer | What You Get |
|
|
29
29
|
|-------|--------------|
|
|
@@ -43,7 +43,7 @@ One `mcp-install` connects your agent to all of it.
|
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
45
|
# Install
|
|
46
|
-
curl -fsSL https://apiclaw.
|
|
46
|
+
curl -fsSL https://apiclaw.cloud/install.sh | bash
|
|
47
47
|
|
|
48
48
|
# Restart your AI assistant
|
|
49
49
|
# Immediately use any API (10 calls/week)
|
|
@@ -194,7 +194,7 @@ Public APIs, instantly callable. No API keys needed.
|
|
|
194
194
|
- Food & Recipes
|
|
195
195
|
- Science & Education
|
|
196
196
|
|
|
197
|
-
Browse at [apiclaw.
|
|
197
|
+
Browse at [apiclaw.cloud/open-apis](https://apiclaw.cloud/open-apis)
|
|
198
198
|
|
|
199
199
|
---
|
|
200
200
|
|
|
@@ -209,7 +209,7 @@ Use `discover_apis` to search:
|
|
|
209
209
|
"What APIs exist for recipe data?"
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
-
Browse at [apiclaw.
|
|
212
|
+
Browse at [apiclaw.cloud/discover](https://apiclaw.cloud/discover)
|
|
213
213
|
|
|
214
214
|
---
|
|
215
215
|
|
|
@@ -218,7 +218,7 @@ Browse at [apiclaw.nordsym.com/discover](https://apiclaw.nordsym.com/discover)
|
|
|
218
218
|
### One-Line Install (Recommended)
|
|
219
219
|
|
|
220
220
|
```bash
|
|
221
|
-
curl -fsSL https://apiclaw.
|
|
221
|
+
curl -fsSL https://apiclaw.cloud/install.sh | bash
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
Auto-detects Claude Desktop or Claude Code. Configures MCP. Done.
|
|
@@ -404,7 +404,7 @@ You don't manage API keys. You don't configure auth. You don't worry about rate
|
|
|
404
404
|
- Usage analytics in your workspace
|
|
405
405
|
- Production-ready from day one
|
|
406
406
|
|
|
407
|
-
**Want higher limits?** See [Pricing](#pricing) below or upgrade at [apiclaw.
|
|
407
|
+
**Want higher limits?** See [Pricing](#pricing) below or upgrade at [apiclaw.cloud](https://apiclaw.cloud).
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
@@ -456,7 +456,7 @@ APIClaw is purpose-built for AI agents and MCP clients. No glue code, no key jug
|
|
|
456
456
|
| **Scale** | $249/mo | 25,000 | Production agents |
|
|
457
457
|
| **Enterprise** | Custom | Unlimited | Large teams & custom SLAs |
|
|
458
458
|
|
|
459
|
-
→ [Upgrade at apiclaw.
|
|
459
|
+
→ [Upgrade at apiclaw.cloud](https://apiclaw.cloud) · Enterprise: [book a call](https://apiclaw.cloud/contact)
|
|
460
460
|
|
|
461
461
|
---
|
|
462
462
|
|
|
@@ -464,9 +464,9 @@ APIClaw is purpose-built for AI agents and MCP clients. No glue code, no key jug
|
|
|
464
464
|
|
|
465
465
|
APIClaw is optimized for discovery by AI agents and LLM tooling:
|
|
466
466
|
|
|
467
|
-
- **llms.txt:** [apiclaw.
|
|
468
|
-
- **llms-full.txt:** [apiclaw.
|
|
469
|
-
- **ai-plugin.json:** [apiclaw.
|
|
467
|
+
- **llms.txt:** [apiclaw.cloud/llms.txt](https://apiclaw.cloud/llms.txt) — Machine-readable API index
|
|
468
|
+
- **llms-full.txt:** [apiclaw.cloud/llms-full.txt](https://apiclaw.cloud/llms-full.txt) — Full capability descriptions
|
|
469
|
+
- **ai-plugin.json:** [apiclaw.cloud/.well-known/ai-plugin.json](https://apiclaw.cloud/.well-known/ai-plugin.json) — OpenAI plugin manifest
|
|
470
470
|
- **MCP:** Install with one command and any MCP-compatible agent can use APIClaw immediately
|
|
471
471
|
|
|
472
472
|
---
|
|
@@ -480,8 +480,8 @@ APIClaw is optimized for discovery by AI agents and LLM tooling:
|
|
|
480
480
|
|
|
481
481
|
## Links
|
|
482
482
|
|
|
483
|
-
- **Platform:** [apiclaw.
|
|
484
|
-
- **Docs:** [apiclaw.
|
|
483
|
+
- **Platform:** [apiclaw.cloud](https://apiclaw.cloud)
|
|
484
|
+
- **Docs:** [apiclaw.cloud/docs](https://apiclaw.cloud/docs)
|
|
485
485
|
- **GitHub:** [github.com/nordsym/apiclaw](https://github.com/nordsym/apiclaw)
|
|
486
486
|
- **npm:** [@nordsym/apiclaw](https://www.npmjs.com/package/@nordsym/apiclaw)
|
|
487
487
|
- **Security:** [SECURITY.md](SECURITY.md)
|
|
@@ -12,6 +12,7 @@ import type * as adminActivate from "../adminActivate.js";
|
|
|
12
12
|
import type * as adminStats from "../adminStats.js";
|
|
13
13
|
import type * as agents from "../agents.js";
|
|
14
14
|
import type * as analytics from "../analytics.js";
|
|
15
|
+
import type * as apiKeys from "../apiKeys.js";
|
|
15
16
|
import type * as backfillAnalytics from "../backfillAnalytics.js";
|
|
16
17
|
import type * as backfillSearchLogs from "../backfillSearchLogs.js";
|
|
17
18
|
import type * as billing from "../billing.js";
|
|
@@ -49,6 +50,7 @@ import type * as updateAPIStatus from "../updateAPIStatus.js";
|
|
|
49
50
|
import type * as usage from "../usage.js";
|
|
50
51
|
import type * as waitlist from "../waitlist.js";
|
|
51
52
|
import type * as webhooks from "../webhooks.js";
|
|
53
|
+
import type * as workspaceSettings from "../workspaceSettings.js";
|
|
52
54
|
import type * as workspaces from "../workspaces.js";
|
|
53
55
|
|
|
54
56
|
import type {
|
|
@@ -62,6 +64,7 @@ declare const fullApi: ApiFromModules<{
|
|
|
62
64
|
adminStats: typeof adminStats;
|
|
63
65
|
agents: typeof agents;
|
|
64
66
|
analytics: typeof analytics;
|
|
67
|
+
apiKeys: typeof apiKeys;
|
|
65
68
|
backfillAnalytics: typeof backfillAnalytics;
|
|
66
69
|
backfillSearchLogs: typeof backfillSearchLogs;
|
|
67
70
|
billing: typeof billing;
|
|
@@ -99,6 +102,7 @@ declare const fullApi: ApiFromModules<{
|
|
|
99
102
|
usage: typeof usage;
|
|
100
103
|
waitlist: typeof waitlist;
|
|
101
104
|
webhooks: typeof webhooks;
|
|
105
|
+
workspaceSettings: typeof workspaceSettings;
|
|
102
106
|
workspaces: typeof workspaces;
|
|
103
107
|
}>;
|
|
104
108
|
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { v } from "convex/values";
|
|
2
|
+
import { mutation, query, internalQuery } from "./_generated/server";
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// WORKSPACE API KEYS
|
|
6
|
+
// Generate persistent API keys for programmatic access.
|
|
7
|
+
// Users generate keys in the dashboard, then use them
|
|
8
|
+
// in any agent config, automation, or script.
|
|
9
|
+
// ============================================
|
|
10
|
+
|
|
11
|
+
function generateRawKey(): string {
|
|
12
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
13
|
+
let result = "";
|
|
14
|
+
for (let i = 0; i < 48; i++) {
|
|
15
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
16
|
+
}
|
|
17
|
+
return `sk-claw-${result}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Simple hash for key lookup (SHA-256 not available in Convex runtime, use deterministic hash)
|
|
21
|
+
function hashKey(key: string): string {
|
|
22
|
+
let hash = 0;
|
|
23
|
+
for (let i = 0; i < key.length; i++) {
|
|
24
|
+
const char = key.charCodeAt(i);
|
|
25
|
+
hash = ((hash << 5) - hash + char) | 0;
|
|
26
|
+
}
|
|
27
|
+
// Create a longer hash by running multiple rounds with offsets
|
|
28
|
+
let h1 = hash;
|
|
29
|
+
let h2 = 0;
|
|
30
|
+
for (let i = 0; i < key.length; i++) {
|
|
31
|
+
h2 = ((h2 << 7) - h2 + key.charCodeAt(i) * 31) | 0;
|
|
32
|
+
}
|
|
33
|
+
let h3 = 0;
|
|
34
|
+
for (let i = 0; i < key.length; i++) {
|
|
35
|
+
h3 = ((h3 << 11) - h3 + key.charCodeAt(i) * 127) | 0;
|
|
36
|
+
}
|
|
37
|
+
return `${(h1 >>> 0).toString(36)}-${(h2 >>> 0).toString(36)}-${(h3 >>> 0).toString(36)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getKeyPrefix(key: string): string {
|
|
41
|
+
return `sk-claw-...${key.slice(-4)}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ============================================
|
|
45
|
+
// GENERATE KEY
|
|
46
|
+
// ============================================
|
|
47
|
+
|
|
48
|
+
export const generateKey = mutation({
|
|
49
|
+
args: {
|
|
50
|
+
token: v.string(), // session token for auth
|
|
51
|
+
name: v.string(), // user label
|
|
52
|
+
},
|
|
53
|
+
handler: async (ctx, args) => {
|
|
54
|
+
// Auth via agentSession
|
|
55
|
+
const session = await ctx.db
|
|
56
|
+
.query("agentSessions")
|
|
57
|
+
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
|
|
58
|
+
.first();
|
|
59
|
+
|
|
60
|
+
if (!session) {
|
|
61
|
+
throw new Error("Invalid session");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const workspaceId = session.workspaceId;
|
|
65
|
+
|
|
66
|
+
// Limit: max 5 active keys per workspace
|
|
67
|
+
const existingKeys = await ctx.db
|
|
68
|
+
.query("workspaceApiKeys")
|
|
69
|
+
.withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspaceId))
|
|
70
|
+
.collect();
|
|
71
|
+
|
|
72
|
+
const activeKeys = existingKeys.filter((k) => !k.revokedAt);
|
|
73
|
+
if (activeKeys.length >= 5) {
|
|
74
|
+
throw new Error("Maximum 5 active keys per workspace. Revoke an existing key first.");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check for duplicate name
|
|
78
|
+
const nameExists = activeKeys.some(
|
|
79
|
+
(k) => k.name.toLowerCase() === args.name.toLowerCase()
|
|
80
|
+
);
|
|
81
|
+
if (nameExists) {
|
|
82
|
+
throw new Error(`A key named "${args.name}" already exists.`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const rawKey = generateRawKey();
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
|
|
88
|
+
await ctx.db.insert("workspaceApiKeys", {
|
|
89
|
+
workspaceId,
|
|
90
|
+
key: "", // We don't store the raw key
|
|
91
|
+
keyHash: hashKey(rawKey),
|
|
92
|
+
keyPrefix: getKeyPrefix(rawKey),
|
|
93
|
+
name: args.name,
|
|
94
|
+
createdAt: now,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Return the raw key ONCE - it won't be retrievable again
|
|
98
|
+
return {
|
|
99
|
+
key: rawKey,
|
|
100
|
+
keyPrefix: getKeyPrefix(rawKey),
|
|
101
|
+
name: args.name,
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ============================================
|
|
107
|
+
// LIST KEYS
|
|
108
|
+
// ============================================
|
|
109
|
+
|
|
110
|
+
export const listKeys = query({
|
|
111
|
+
args: {
|
|
112
|
+
token: v.string(),
|
|
113
|
+
},
|
|
114
|
+
handler: async (ctx, args) => {
|
|
115
|
+
const session = await ctx.db
|
|
116
|
+
.query("agentSessions")
|
|
117
|
+
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
|
|
118
|
+
.first();
|
|
119
|
+
|
|
120
|
+
if (!session) {
|
|
121
|
+
return { keys: [] };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const keys = await ctx.db
|
|
125
|
+
.query("workspaceApiKeys")
|
|
126
|
+
.withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
|
|
127
|
+
.collect();
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
keys: keys
|
|
131
|
+
.filter((k) => !k.revokedAt)
|
|
132
|
+
.map((k) => ({
|
|
133
|
+
id: k._id,
|
|
134
|
+
name: k.name,
|
|
135
|
+
keyPrefix: k.keyPrefix,
|
|
136
|
+
lastUsedAt: k.lastUsedAt,
|
|
137
|
+
createdAt: k.createdAt,
|
|
138
|
+
}))
|
|
139
|
+
.sort((a, b) => b.createdAt - a.createdAt),
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// ============================================
|
|
145
|
+
// REVOKE KEY
|
|
146
|
+
// ============================================
|
|
147
|
+
|
|
148
|
+
export const revokeKey = mutation({
|
|
149
|
+
args: {
|
|
150
|
+
token: v.string(),
|
|
151
|
+
keyId: v.id("workspaceApiKeys"),
|
|
152
|
+
},
|
|
153
|
+
handler: async (ctx, args) => {
|
|
154
|
+
const session = await ctx.db
|
|
155
|
+
.query("agentSessions")
|
|
156
|
+
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
|
|
157
|
+
.first();
|
|
158
|
+
|
|
159
|
+
if (!session) {
|
|
160
|
+
throw new Error("Invalid session");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const key = await ctx.db.get(args.keyId);
|
|
164
|
+
if (!key || key.workspaceId !== session.workspaceId) {
|
|
165
|
+
throw new Error("Key not found");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (key.revokedAt) {
|
|
169
|
+
throw new Error("Key already revoked");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await ctx.db.patch(args.keyId, { revokedAt: Date.now() });
|
|
173
|
+
return { success: true };
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// ============================================
|
|
178
|
+
// RESOLVE KEY (internal - used by gateway)
|
|
179
|
+
// ============================================
|
|
180
|
+
|
|
181
|
+
export const resolveKey = internalQuery({
|
|
182
|
+
args: {
|
|
183
|
+
rawKey: v.string(),
|
|
184
|
+
},
|
|
185
|
+
handler: async (ctx, args) => {
|
|
186
|
+
const keyHash = hashKey(args.rawKey);
|
|
187
|
+
|
|
188
|
+
const keyDoc = await ctx.db
|
|
189
|
+
.query("workspaceApiKeys")
|
|
190
|
+
.withIndex("by_keyHash", (q) => q.eq("keyHash", keyHash))
|
|
191
|
+
.first();
|
|
192
|
+
|
|
193
|
+
if (!keyDoc) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (keyDoc.revokedAt) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
workspaceId: keyDoc.workspaceId,
|
|
203
|
+
keyId: keyDoc._id,
|
|
204
|
+
name: keyDoc.name,
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ============================================
|
|
210
|
+
// TOUCH KEY (internal - update lastUsedAt)
|
|
211
|
+
// ============================================
|
|
212
|
+
|
|
213
|
+
export const touchKey = mutation({
|
|
214
|
+
args: {
|
|
215
|
+
keyId: v.id("workspaceApiKeys"),
|
|
216
|
+
},
|
|
217
|
+
handler: async (ctx, args) => {
|
|
218
|
+
await ctx.db.patch(args.keyId, { lastUsedAt: Date.now() });
|
|
219
|
+
},
|
|
220
|
+
});
|
package/convex/directCall.ts
CHANGED
|
@@ -82,14 +82,31 @@ export const saveConfig = mutation({
|
|
|
82
82
|
}),
|
|
83
83
|
},
|
|
84
84
|
handler: async (ctx, args) => {
|
|
85
|
-
// Verify session
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
// Verify session (unified: agentSessions first, fallback to legacy sessions)
|
|
86
|
+
let providerId: any = null;
|
|
87
|
+
|
|
88
|
+
const agentSession = await ctx.db
|
|
89
|
+
.query("agentSessions")
|
|
90
|
+
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
|
|
89
91
|
.first();
|
|
90
92
|
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
+
if (agentSession) {
|
|
94
|
+
const provider = await ctx.db
|
|
95
|
+
.query("providers")
|
|
96
|
+
.withIndex("by_workspaceId", (q) => q.eq("workspaceId", agentSession.workspaceId))
|
|
97
|
+
.first();
|
|
98
|
+
if (!provider) throw new Error("No provider linked to workspace");
|
|
99
|
+
providerId = provider._id;
|
|
100
|
+
} else {
|
|
101
|
+
// Fallback: legacy sessions
|
|
102
|
+
const session = await ctx.db
|
|
103
|
+
.query("sessions")
|
|
104
|
+
.withIndex("by_token", (q) => q.eq("token", args.token))
|
|
105
|
+
.first();
|
|
106
|
+
if (!session || session.expiresAt < Date.now()) {
|
|
107
|
+
throw new Error("Invalid or expired session");
|
|
108
|
+
}
|
|
109
|
+
providerId = session.providerId;
|
|
93
110
|
}
|
|
94
111
|
|
|
95
112
|
const now = Date.now();
|
|
@@ -128,7 +145,7 @@ export const saveConfig = mutation({
|
|
|
128
145
|
|
|
129
146
|
// Create new config
|
|
130
147
|
return await ctx.db.insert("providerDirectCall", {
|
|
131
|
-
providerId
|
|
148
|
+
providerId,
|
|
132
149
|
apiId: config.apiId as any,
|
|
133
150
|
baseUrl: config.baseUrl,
|
|
134
151
|
authType: config.authType,
|
|
@@ -536,13 +553,31 @@ export const testAction = mutation({
|
|
|
536
553
|
handler: async (ctx, args) => {
|
|
537
554
|
const startTime = Date.now();
|
|
538
555
|
|
|
539
|
-
// 1. Verify provider session
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
556
|
+
// 1. Verify provider session (unified: agentSessions first, fallback to legacy)
|
|
557
|
+
let sessionProviderId: any = null;
|
|
558
|
+
|
|
559
|
+
const agentSession = await ctx.db
|
|
560
|
+
.query("agentSessions")
|
|
561
|
+
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
|
|
543
562
|
.first();
|
|
544
|
-
|
|
545
|
-
if (
|
|
563
|
+
|
|
564
|
+
if (agentSession) {
|
|
565
|
+
const provider = await ctx.db
|
|
566
|
+
.query("providers")
|
|
567
|
+
.withIndex("by_workspaceId", (q) => q.eq("workspaceId", agentSession.workspaceId))
|
|
568
|
+
.first();
|
|
569
|
+
if (provider) sessionProviderId = provider._id;
|
|
570
|
+
} else {
|
|
571
|
+
const session = await ctx.db
|
|
572
|
+
.query("sessions")
|
|
573
|
+
.withIndex("by_token", (q) => q.eq("token", args.token))
|
|
574
|
+
.first();
|
|
575
|
+
if (session && session.expiresAt >= Date.now()) {
|
|
576
|
+
sessionProviderId = session.providerId;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (!sessionProviderId) {
|
|
546
581
|
return {
|
|
547
582
|
success: false,
|
|
548
583
|
error: "Unauthorized - invalid or expired session",
|
|
@@ -561,7 +596,7 @@ export const testAction = mutation({
|
|
|
561
596
|
}
|
|
562
597
|
|
|
563
598
|
// Verify ownership
|
|
564
|
-
if (config.providerId !==
|
|
599
|
+
if (config.providerId !== sessionProviderId) {
|
|
565
600
|
return {
|
|
566
601
|
success: false,
|
|
567
602
|
error: "Unauthorized - you don't own this config",
|
package/convex/email.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { action, internalAction } from "./_generated/server";
|
|
|
5
5
|
// EMAIL TEMPLATES
|
|
6
6
|
// ============================================
|
|
7
7
|
|
|
8
|
-
const EMAIL_FROM = "APIClaw <noreply@apiclaw.
|
|
9
|
-
const APP_URL = "https://apiclaw.
|
|
8
|
+
const EMAIL_FROM = "APIClaw <noreply@apiclaw.cloud>";
|
|
9
|
+
const APP_URL = "https://apiclaw.cloud";
|
|
10
10
|
|
|
11
11
|
// Base email wrapper - using string concat for Convex compatibility
|
|
12
12
|
function wrapEmail(content: string): string {
|
|
@@ -37,7 +37,7 @@ function wrapEmail(content: string): string {
|
|
|
37
37
|
'<tr>',
|
|
38
38
|
'<td style="padding: 24px 32px; background-color: #fafafa; border-top: 1px solid #f0f0f0;">',
|
|
39
39
|
'<p style="margin: 0; font-size: 12px; color: #737373; text-align: center;">',
|
|
40
|
-
'<a href="https://apiclaw.
|
|
40
|
+
'<a href="https://apiclaw.cloud" style="color: #ef4444; text-decoration: none;">APIClaw</a> — The API Layer for AI Agents',
|
|
41
41
|
'</p>',
|
|
42
42
|
'<p style="margin: 8px 0 0; font-size: 12px; color: #a3a3a3; text-align: center;">',
|
|
43
43
|
'© ' + year + ' NordSym. Stockholm, Sweden.',
|
|
@@ -283,7 +283,7 @@ export const debugEmailTemplate = action({
|
|
|
283
283
|
args: { email: v.string() },
|
|
284
284
|
handler: async (ctx, { email }) => {
|
|
285
285
|
const RESEND_API_KEY = process.env.RESEND_API_KEY;
|
|
286
|
-
const testUrl = "https://apiclaw.
|
|
286
|
+
const testUrl = "https://apiclaw.cloud/auth/verify?token=DEBUG_TEST";
|
|
287
287
|
|
|
288
288
|
// Generate HTML using the template
|
|
289
289
|
var html = "<!DOCTYPE html><html><head><meta charset='utf-8'></head>";
|
|
@@ -309,7 +309,7 @@ export const debugEmailTemplate = action({
|
|
|
309
309
|
"Content-Type": "application/json",
|
|
310
310
|
},
|
|
311
311
|
body: JSON.stringify({
|
|
312
|
-
from: "APIClaw <noreply@apiclaw.
|
|
312
|
+
from: "APIClaw <noreply@apiclaw.cloud>",
|
|
313
313
|
to: email,
|
|
314
314
|
subject: "DEBUG EMAIL FROM CONVEX",
|
|
315
315
|
html: html,
|