@nordsym/apiclaw 1.7.1 → 1.7.2
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/package.json +1 -1
- package/src/capability-router.ts +1 -1
- package/src/execute.ts +20 -5
- package/src/http-api.ts +1 -1
- package/src/index.ts +1 -1
- package/dist/analytics.d.ts +0 -32
- package/dist/analytics.d.ts.map +0 -1
- package/dist/analytics.js +0 -130
- package/dist/analytics.js.map +0 -1
- package/dist/src/analytics.js +0 -129
- /package/src/{analytics.ts → mcp-analytics.ts} +0 -0
package/package.json
CHANGED
package/src/capability-router.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { executeAPICall } from './execute.js';
|
|
7
|
-
import { logAPICall } from './analytics.js';
|
|
7
|
+
import { logAPICall } from './mcp-analytics.js';
|
|
8
8
|
|
|
9
9
|
// Convex HTTP API for capability queries
|
|
10
10
|
const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://adventurous-avocet-799.convex.cloud';
|
package/src/execute.ts
CHANGED
|
@@ -2256,11 +2256,26 @@ export async function getProviderActionsAsync(providerId: string): Promise<strin
|
|
|
2256
2256
|
}
|
|
2257
2257
|
|
|
2258
2258
|
// Get all connected providers with their actions (static handlers only)
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2259
|
+
// APILayer actions blocked by subscription tier
|
|
2260
|
+
const BLOCKED_ACTIONS = ['verify_number', 'world_news', 'image_crop', 'form_submit'];
|
|
2261
|
+
const RATE_LIMITED_ACTIONS = ['pdf_generate'];
|
|
2262
|
+
|
|
2263
|
+
export function getConnectedProviders(): { provider: string; actions: string[]; blocked?: string[]; rate_limited?: string[] }[] {
|
|
2264
|
+
return Object.entries(handlers).map(([provider, actions]) => {
|
|
2265
|
+
const allActions = Object.keys(actions);
|
|
2266
|
+
if (provider === 'apilayer') {
|
|
2267
|
+
const live = allActions.filter(a => !BLOCKED_ACTIONS.includes(a) && !RATE_LIMITED_ACTIONS.includes(a));
|
|
2268
|
+
const blocked = allActions.filter(a => BLOCKED_ACTIONS.includes(a));
|
|
2269
|
+
const rateLimited = allActions.filter(a => RATE_LIMITED_ACTIONS.includes(a));
|
|
2270
|
+
return {
|
|
2271
|
+
provider,
|
|
2272
|
+
actions: live,
|
|
2273
|
+
...(blocked.length > 0 ? { blocked } : {}),
|
|
2274
|
+
...(rateLimited.length > 0 ? { rate_limited: rateLimited } : {}),
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
return { provider, actions: allActions };
|
|
2278
|
+
});
|
|
2264
2279
|
}
|
|
2265
2280
|
|
|
2266
2281
|
// Execute an API call
|
package/src/http-api.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { URL } from 'url';
|
|
|
15
15
|
import { discoverAPIs } from './discovery.js';
|
|
16
16
|
import { isOpenAPI, executeOpenAPI } from './open-apis.js';
|
|
17
17
|
import { executeMetered } from './metered.js';
|
|
18
|
-
import { logAPICall } from './analytics.js';
|
|
18
|
+
import { logAPICall } from './mcp-analytics.js';
|
|
19
19
|
import { getMachineFingerprint } from './session.js';
|
|
20
20
|
import { isAuthorized, getProduct } from './product-whitelist.js';
|
|
21
21
|
|
package/src/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
import { hasRealCredentials } from './credentials.js';
|
|
32
32
|
import { getConnectedProviders } from './execute.js';
|
|
33
33
|
import { executeMetered } from './metered.js';
|
|
34
|
-
import { logAPICall } from './analytics.js';
|
|
34
|
+
import { logAPICall } from './mcp-analytics.js';
|
|
35
35
|
import { isOpenAPI, executeOpenAPI, listOpenAPIs, getOpenAPIActions } from './open-apis.js';
|
|
36
36
|
import { PROXY_PROVIDERS } from './proxy.js';
|
|
37
37
|
import {
|
package/dist/analytics.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* APIClaw Analytics - Track all API usage
|
|
3
|
-
* Both Direct Call and Open API calls are logged here
|
|
4
|
-
*/
|
|
5
|
-
export interface APICallLog {
|
|
6
|
-
timestamp: string;
|
|
7
|
-
provider: string;
|
|
8
|
-
action: string;
|
|
9
|
-
type: 'direct' | 'open';
|
|
10
|
-
userId?: string;
|
|
11
|
-
success: boolean;
|
|
12
|
-
latencyMs?: number;
|
|
13
|
-
error?: string;
|
|
14
|
-
metadata?: {
|
|
15
|
-
product?: string;
|
|
16
|
-
[key: string]: any;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Log an API call to local file AND Convex
|
|
21
|
-
*/
|
|
22
|
-
export declare function logAPICall(log: APICallLog): void;
|
|
23
|
-
/**
|
|
24
|
-
* Webhook for real-time analytics (optional)
|
|
25
|
-
* Set APICLAW_ANALYTICS_WEBHOOK env var to enable
|
|
26
|
-
*/
|
|
27
|
-
export declare function sendToWebhook(log: APICallLog): Promise<void>;
|
|
28
|
-
/**
|
|
29
|
-
* Track API call with timing
|
|
30
|
-
*/
|
|
31
|
-
export declare function trackAPICall<T>(provider: string, action: string, type: 'direct' | 'open', userId: string | undefined, fn: () => Promise<T>): Promise<T>;
|
|
32
|
-
//# sourceMappingURL=analytics.d.ts.map
|
package/dist/analytics.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE;QACT,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;CACH;AA+BD;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,CAYhD;AA+BD;;;GAGG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAalE;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,GAAG,MAAM,EACvB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CA0BZ"}
|
package/dist/analytics.js
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* APIClaw Analytics - Track all API usage
|
|
3
|
-
* Both Direct Call and Open API calls are logged here
|
|
4
|
-
*/
|
|
5
|
-
import { appendFileSync, existsSync, mkdirSync } from 'fs';
|
|
6
|
-
import { homedir } from 'os';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
import { ConvexHttpClient } from 'convex/browser';
|
|
9
|
-
import { api } from '../convex/_generated/api.js';
|
|
10
|
-
// Log directory
|
|
11
|
-
const LOG_DIR = join(homedir(), '.apiclaw', 'logs');
|
|
12
|
-
const LOG_FILE = join(LOG_DIR, 'api-calls.jsonl');
|
|
13
|
-
// Convex client (lazy init)
|
|
14
|
-
let convexClient = null;
|
|
15
|
-
function getConvexClient() {
|
|
16
|
-
if (convexClient)
|
|
17
|
-
return convexClient;
|
|
18
|
-
const convexUrl = process.env.APICLAW_CONVEX_URL || process.env.NEXT_PUBLIC_CONVEX_URL;
|
|
19
|
-
if (!convexUrl)
|
|
20
|
-
return null;
|
|
21
|
-
try {
|
|
22
|
-
convexClient = new ConvexHttpClient(convexUrl);
|
|
23
|
-
return convexClient;
|
|
24
|
-
}
|
|
25
|
-
catch (e) {
|
|
26
|
-
console.error('[APIClaw Analytics] Failed to init Convex client:', e);
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// Ensure log directory exists
|
|
31
|
-
function ensureLogDir() {
|
|
32
|
-
if (!existsSync(LOG_DIR)) {
|
|
33
|
-
mkdirSync(LOG_DIR, { recursive: true });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Log an API call to local file AND Convex
|
|
38
|
-
*/
|
|
39
|
-
export function logAPICall(log) {
|
|
40
|
-
// 1. Local file (existing behavior)
|
|
41
|
-
try {
|
|
42
|
-
ensureLogDir();
|
|
43
|
-
const line = JSON.stringify(log) + '\n';
|
|
44
|
-
appendFileSync(LOG_FILE, line);
|
|
45
|
-
}
|
|
46
|
-
catch (e) {
|
|
47
|
-
console.error('[APIClaw Analytics] Failed to log to file:', e);
|
|
48
|
-
}
|
|
49
|
-
// 2. Send to Convex (new - includes anonymous usage)
|
|
50
|
-
sendToConvex(log).catch(() => { }); // Fire and forget
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Send analytics event to Convex
|
|
54
|
-
* Works for both authenticated and anonymous users
|
|
55
|
-
*/
|
|
56
|
-
async function sendToConvex(log) {
|
|
57
|
-
const client = getConvexClient();
|
|
58
|
-
if (!client)
|
|
59
|
-
return;
|
|
60
|
-
try {
|
|
61
|
-
await client.mutation(api.analytics.log, {
|
|
62
|
-
event: 'api_call',
|
|
63
|
-
provider: log.provider,
|
|
64
|
-
query: log.action, // Store action as query field
|
|
65
|
-
identifier: log.userId || 'anonymous',
|
|
66
|
-
metadata: {
|
|
67
|
-
type: log.type,
|
|
68
|
-
success: log.success,
|
|
69
|
-
latencyMs: log.latencyMs,
|
|
70
|
-
error: log.error,
|
|
71
|
-
timestamp: log.timestamp,
|
|
72
|
-
...log.metadata, // Include product and any other metadata
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
catch (e) {
|
|
77
|
-
// Silent fail - don't break API calls for logging errors
|
|
78
|
-
console.error('[APIClaw Analytics] Failed to send to Convex:', e);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Webhook for real-time analytics (optional)
|
|
83
|
-
* Set APICLAW_ANALYTICS_WEBHOOK env var to enable
|
|
84
|
-
*/
|
|
85
|
-
export async function sendToWebhook(log) {
|
|
86
|
-
const webhookUrl = process.env.APICLAW_ANALYTICS_WEBHOOK;
|
|
87
|
-
if (!webhookUrl)
|
|
88
|
-
return;
|
|
89
|
-
try {
|
|
90
|
-
await fetch(webhookUrl, {
|
|
91
|
-
method: 'POST',
|
|
92
|
-
headers: { 'Content-Type': 'application/json' },
|
|
93
|
-
body: JSON.stringify(log),
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
catch (e) {
|
|
97
|
-
// Silent fail
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Track API call with timing
|
|
102
|
-
*/
|
|
103
|
-
export async function trackAPICall(provider, action, type, userId, fn) {
|
|
104
|
-
const start = Date.now();
|
|
105
|
-
let success = true;
|
|
106
|
-
let error;
|
|
107
|
-
try {
|
|
108
|
-
return await fn();
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
success = false;
|
|
112
|
-
error = e.message;
|
|
113
|
-
throw e;
|
|
114
|
-
}
|
|
115
|
-
finally {
|
|
116
|
-
const log = {
|
|
117
|
-
timestamp: new Date().toISOString(),
|
|
118
|
-
provider,
|
|
119
|
-
action,
|
|
120
|
-
type,
|
|
121
|
-
userId,
|
|
122
|
-
success,
|
|
123
|
-
latencyMs: Date.now() - start,
|
|
124
|
-
error,
|
|
125
|
-
};
|
|
126
|
-
logAPICall(log);
|
|
127
|
-
sendToWebhook(log).catch(() => { }); // Fire and forget
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
//# sourceMappingURL=analytics.js.map
|
package/dist/analytics.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
package/dist/src/analytics.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* APIClaw Analytics - Track all API usage
|
|
3
|
-
* Both Direct Call and Open API calls are logged here
|
|
4
|
-
*/
|
|
5
|
-
import { appendFileSync, existsSync, mkdirSync } from 'fs';
|
|
6
|
-
import { homedir } from 'os';
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
import { ConvexHttpClient } from 'convex/browser';
|
|
9
|
-
import { api } from '../convex/_generated/api.js';
|
|
10
|
-
// Log directory
|
|
11
|
-
const LOG_DIR = join(homedir(), '.apiclaw', 'logs');
|
|
12
|
-
const LOG_FILE = join(LOG_DIR, 'api-calls.jsonl');
|
|
13
|
-
// Convex client (lazy init)
|
|
14
|
-
let convexClient = null;
|
|
15
|
-
function getConvexClient() {
|
|
16
|
-
if (convexClient)
|
|
17
|
-
return convexClient;
|
|
18
|
-
const convexUrl = process.env.APICLAW_CONVEX_URL || process.env.NEXT_PUBLIC_CONVEX_URL;
|
|
19
|
-
if (!convexUrl)
|
|
20
|
-
return null;
|
|
21
|
-
try {
|
|
22
|
-
convexClient = new ConvexHttpClient(convexUrl);
|
|
23
|
-
return convexClient;
|
|
24
|
-
}
|
|
25
|
-
catch (e) {
|
|
26
|
-
console.error('[APIClaw Analytics] Failed to init Convex client:', e);
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
// Ensure log directory exists
|
|
31
|
-
function ensureLogDir() {
|
|
32
|
-
if (!existsSync(LOG_DIR)) {
|
|
33
|
-
mkdirSync(LOG_DIR, { recursive: true });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Log an API call to local file AND Convex
|
|
38
|
-
*/
|
|
39
|
-
export function logAPICall(log) {
|
|
40
|
-
// 1. Local file (existing behavior)
|
|
41
|
-
try {
|
|
42
|
-
ensureLogDir();
|
|
43
|
-
const line = JSON.stringify(log) + '\n';
|
|
44
|
-
appendFileSync(LOG_FILE, line);
|
|
45
|
-
}
|
|
46
|
-
catch (e) {
|
|
47
|
-
console.error('[APIClaw Analytics] Failed to log to file:', e);
|
|
48
|
-
}
|
|
49
|
-
// 2. Send to Convex (new - includes anonymous usage)
|
|
50
|
-
sendToConvex(log).catch(() => { }); // Fire and forget
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Send analytics event to Convex
|
|
54
|
-
* Works for both authenticated and anonymous users
|
|
55
|
-
*/
|
|
56
|
-
async function sendToConvex(log) {
|
|
57
|
-
const client = getConvexClient();
|
|
58
|
-
if (!client)
|
|
59
|
-
return;
|
|
60
|
-
try {
|
|
61
|
-
await client.mutation(api.analytics.log, {
|
|
62
|
-
event: 'api_call',
|
|
63
|
-
provider: log.provider,
|
|
64
|
-
query: log.action, // Store action as query field
|
|
65
|
-
identifier: log.userId || 'anonymous',
|
|
66
|
-
metadata: {
|
|
67
|
-
type: log.type,
|
|
68
|
-
success: log.success,
|
|
69
|
-
latencyMs: log.latencyMs,
|
|
70
|
-
error: log.error,
|
|
71
|
-
timestamp: log.timestamp,
|
|
72
|
-
...log.metadata, // Include product and any other metadata
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
catch (e) {
|
|
77
|
-
// Silent fail - don't break API calls for logging errors
|
|
78
|
-
console.error('[APIClaw Analytics] Failed to send to Convex:', e);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Webhook for real-time analytics (optional)
|
|
83
|
-
* Set APICLAW_ANALYTICS_WEBHOOK env var to enable
|
|
84
|
-
*/
|
|
85
|
-
export async function sendToWebhook(log) {
|
|
86
|
-
const webhookUrl = process.env.APICLAW_ANALYTICS_WEBHOOK;
|
|
87
|
-
if (!webhookUrl)
|
|
88
|
-
return;
|
|
89
|
-
try {
|
|
90
|
-
await fetch(webhookUrl, {
|
|
91
|
-
method: 'POST',
|
|
92
|
-
headers: { 'Content-Type': 'application/json' },
|
|
93
|
-
body: JSON.stringify(log),
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
catch (e) {
|
|
97
|
-
// Silent fail
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Track API call with timing
|
|
102
|
-
*/
|
|
103
|
-
export async function trackAPICall(provider, action, type, userId, fn) {
|
|
104
|
-
const start = Date.now();
|
|
105
|
-
let success = true;
|
|
106
|
-
let error;
|
|
107
|
-
try {
|
|
108
|
-
return await fn();
|
|
109
|
-
}
|
|
110
|
-
catch (e) {
|
|
111
|
-
success = false;
|
|
112
|
-
error = e.message;
|
|
113
|
-
throw e;
|
|
114
|
-
}
|
|
115
|
-
finally {
|
|
116
|
-
const log = {
|
|
117
|
-
timestamp: new Date().toISOString(),
|
|
118
|
-
provider,
|
|
119
|
-
action,
|
|
120
|
-
type,
|
|
121
|
-
userId,
|
|
122
|
-
success,
|
|
123
|
-
latencyMs: Date.now() - start,
|
|
124
|
-
error,
|
|
125
|
-
};
|
|
126
|
-
logAPICall(log);
|
|
127
|
-
sendToWebhook(log).catch(() => { }); // Fire and forget
|
|
128
|
-
}
|
|
129
|
-
}
|
|
File without changes
|