@nordsym/apiclaw 2.2.0 → 2.2.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/README.md +15 -2
- package/dist/bin-http.js +0 -0
- package/dist/bin.bundled.js +79288 -0
- package/dist/gateway-client.d.ts.map +1 -1
- package/dist/gateway-client.js +24 -2
- package/dist/gateway-client.js.map +1 -1
- package/dist/index.bundled.js +61263 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +7 -2
- package/.claude/settings.local.json +0 -13
- package/.env.prod +0 -1
- package/apiclaw-README.md +0 -494
- package/convex/_generated/api.d.ts +0 -145
- package/convex/_generated/api.js +0 -23
- package/convex/_generated/dataModel.d.ts +0 -60
- package/convex/_generated/server.d.ts +0 -143
- package/convex/_generated/server.js +0 -93
- package/convex/_listWorkspaces.ts +0 -13
- package/convex/adminActivate.ts +0 -53
- package/convex/adminStats.ts +0 -306
- package/convex/agents.ts +0 -939
- package/convex/analytics.ts +0 -187
- package/convex/apiKeys.ts +0 -220
- package/convex/backfillAnalytics.ts +0 -272
- package/convex/backfillSearchLogs.ts +0 -35
- package/convex/billing.ts +0 -834
- package/convex/capabilities.ts +0 -157
- package/convex/chains.ts +0 -1318
- package/convex/credits.ts +0 -211
- package/convex/crons.ts +0 -65
- package/convex/debugFilestackLogs.ts +0 -16
- package/convex/debugGetToken.ts +0 -18
- package/convex/directCall.ts +0 -713
- package/convex/earnProgress.ts +0 -753
- package/convex/email.ts +0 -329
- package/convex/feedback.ts +0 -265
- package/convex/funnel.ts +0 -431
- package/convex/guards.ts +0 -174
- package/convex/http.ts +0 -3756
- package/convex/inbound.ts +0 -32
- package/convex/logs.ts +0 -701
- package/convex/migrateFilestack.ts +0 -81
- package/convex/migratePartnersProd.ts +0 -174
- package/convex/migratePratham.ts +0 -126
- package/convex/migrateProviderWorkspaces.ts +0 -175
- package/convex/mou.ts +0 -91
- package/convex/nurture.ts +0 -355
- package/convex/providerKeys.ts +0 -289
- package/convex/providers.ts +0 -1135
- package/convex/purchases.ts +0 -183
- package/convex/ratelimit.ts +0 -104
- package/convex/schema.ts +0 -926
- package/convex/searchLogs.ts +0 -265
- package/convex/seedAPILayerAPIs.ts +0 -191
- package/convex/seedDirectCallConfigs.ts +0 -336
- package/convex/seedPratham.ts +0 -149
- package/convex/spendAlerts.ts +0 -442
- package/convex/stripeActions.ts +0 -607
- package/convex/teams.ts +0 -243
- package/convex/telemetry.ts +0 -81
- package/convex/tsconfig.json +0 -25
- package/convex/updateAPIStatus.ts +0 -44
- package/convex/usage.ts +0 -260
- package/convex/usageReports.ts +0 -357
- package/convex/waitlist.ts +0 -55
- package/convex/webhooks.ts +0 -494
- package/convex/workspaceSettings.ts +0 -143
- package/convex/workspaces.ts +0 -1331
- package/convex.json +0 -3
- package/direct-test.mjs +0 -51
- package/email-templates/filestack-provider-outreach.html +0 -162
- package/email-templates/partnership-template.html +0 -116
- package/email-templates/pratham-draft-preview.txt +0 -57
- package/email-templates/pratham-partnership-draft.html +0 -141
- package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
- package/reports/pipeline/PIPELINE-REPORT.json +0 -153
- package/reports/pipeline/acquire_apisguru.json +0 -17
- package/reports/pipeline/capabilities.json +0 -38
- package/reports/pipeline/discover_azure_recursive.json +0 -1551
- package/reports/pipeline/discover_github.json +0 -25
- package/reports/pipeline/discover_github_repos.json +0 -49
- package/reports/pipeline/discover_swaggerhub.json +0 -24
- package/reports/pipeline/discover_well_known.json +0 -23
- package/reports/pipeline/fetch_specs.json +0 -19
- package/reports/pipeline/generate_providers.json +0 -14
- package/reports/pipeline/match_registry.json +0 -11
- package/reports/pipeline/parse_specs.json +0 -17
- package/reports/pipeline/promote_candidates.json +0 -34
- package/reports/pipeline/validate.json +0 -30
- package/reports/pipeline/validate_smoke_details.json +0 -3835
- package/reports/session-report-2026-04-05.html +0 -433
- package/seed-apis-direct.mjs +0 -106
- package/src/access-control.ts +0 -174
- package/src/adapters/base.ts +0 -364
- package/src/adapters/claude-desktop.ts +0 -41
- package/src/adapters/cline.ts +0 -88
- package/src/adapters/continue.ts +0 -91
- package/src/adapters/cursor.ts +0 -43
- package/src/adapters/custom.ts +0 -188
- package/src/adapters/detect.ts +0 -202
- package/src/adapters/index.ts +0 -47
- package/src/adapters/windsurf.ts +0 -44
- package/src/bin-http.ts +0 -45
- package/src/bin.ts +0 -34
- package/src/capability-router.ts +0 -331
- package/src/chainExecutor.ts +0 -730
- package/src/chainResolver.test.ts +0 -246
- package/src/chainResolver.ts +0 -658
- package/src/cli/commands/demo.ts +0 -109
- package/src/cli/commands/doctor.ts +0 -435
- package/src/cli/commands/index.ts +0 -9
- package/src/cli/commands/login.ts +0 -203
- package/src/cli/commands/mcp-install.ts +0 -373
- package/src/cli/commands/restore.ts +0 -333
- package/src/cli/commands/setup.ts +0 -297
- package/src/cli/commands/uninstall.ts +0 -240
- package/src/cli/index.ts +0 -148
- package/src/cli.ts +0 -370
- package/src/confirmation.ts +0 -296
- package/src/credentials.ts +0 -455
- package/src/credits.ts +0 -329
- package/src/crypto.ts +0 -75
- package/src/discovery.ts +0 -568
- package/src/enterprise/env.ts +0 -156
- package/src/enterprise/index.ts +0 -7
- package/src/enterprise/script-generator.ts +0 -481
- package/src/execute-dynamic.ts +0 -617
- package/src/execute.ts +0 -2386
- package/src/funnel-client.ts +0 -168
- package/src/funnel.test.ts +0 -187
- package/src/gateway-client.ts +0 -192
- package/src/hivr-whitelist.ts +0 -110
- package/src/http-api.ts +0 -286
- package/src/http-server-minimal.ts +0 -154
- package/src/index.ts +0 -2702
- package/src/intelligent-gateway.ts +0 -339
- package/src/mcp-analytics.ts +0 -156
- package/src/metered.ts +0 -149
- package/src/open-apis-generated.ts +0 -157
- package/src/open-apis.ts +0 -558
- package/src/postinstall.ts +0 -40
- package/src/product-whitelist.ts +0 -246
- package/src/proxy.ts +0 -36
- package/src/registration-guard.ts +0 -117
- package/src/session.ts +0 -129
- package/src/stripe.ts +0 -497
- package/src/telemetry.ts +0 -71
- package/src/test.ts +0 -135
- package/src/types/convex-api.d.ts +0 -20
- package/src/types/convex-api.ts +0 -21
- package/src/types.ts +0 -109
- package/src/ui/colors.ts +0 -219
- package/src/ui/errors.ts +0 -394
- package/src/ui/index.ts +0 -17
- package/src/ui/prompts.ts +0 -390
- package/src/ui/spinner.ts +0 -325
- package/src/utils/backup.ts +0 -224
- package/src/utils/config.ts +0 -318
- package/src/utils/os.ts +0 -124
- package/src/utils/paths.ts +0 -203
- package/src/webhook.ts +0 -107
- package/test-10-working.cjs +0 -97
- package/test-14-final.cjs +0 -96
- package/test-actual-handlers.ts +0 -92
- package/test-apilayer-all-14.ts +0 -249
- package/test-apilayer-fixed.ts +0 -248
- package/test-direct-endpoints.ts +0 -174
- package/test-exact-endpoints.ts +0 -144
- package/test-final.ts +0 -83
- package/test-full-routing.ts +0 -100
- package/test-handlers-correct.ts +0 -217
- package/test-numverify-key.ts +0 -41
- package/test-via-handlers.ts +0 -92
- package/test-worldnews.mjs +0 -26
- package/tsconfig.json +0 -20
package/src/funnel-client.ts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* APIClaw Funnel — client-side emitter for MCP server.
|
|
3
|
-
*
|
|
4
|
-
* Fire-and-forget POST to Convex funnel:recordEvent. Never throws, never
|
|
5
|
-
* blocks. See convex/funnel.ts for schema and classification rules.
|
|
6
|
-
*/
|
|
7
|
-
import * as fs from "fs";
|
|
8
|
-
import * as path from "path";
|
|
9
|
-
import * as os from "os";
|
|
10
|
-
|
|
11
|
-
const CONVEX_URL = process.env.CONVEX_URL || "https://adventurous-avocet-799.convex.cloud";
|
|
12
|
-
|
|
13
|
-
export type FunnelEventName =
|
|
14
|
-
| "install"
|
|
15
|
-
| "first_run"
|
|
16
|
-
| "register_owner"
|
|
17
|
-
| "verify_code"
|
|
18
|
-
| "first_call_api_success"
|
|
19
|
-
// Diagnostics
|
|
20
|
-
| "register_owner_failed"
|
|
21
|
-
| "verify_code_failed"
|
|
22
|
-
| "call_api_blocked"
|
|
23
|
-
| "call_api_error"
|
|
24
|
-
| "quota_hit"
|
|
25
|
-
| "gateway_retry";
|
|
26
|
-
|
|
27
|
-
export type Classification = "human" | "ci" | "bot" | "internal";
|
|
28
|
-
|
|
29
|
-
const CI_ENV_KEYS = [
|
|
30
|
-
"CI",
|
|
31
|
-
"GITHUB_ACTIONS",
|
|
32
|
-
"GITLAB_CI",
|
|
33
|
-
"CIRCLECI",
|
|
34
|
-
"BUILDKITE",
|
|
35
|
-
"JENKINS_URL",
|
|
36
|
-
"TEAMCITY_VERSION",
|
|
37
|
-
"TRAVIS",
|
|
38
|
-
"BITBUCKET_BUILD_NUMBER",
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
const BOT_UA_MARKERS = [
|
|
42
|
-
"bot",
|
|
43
|
-
"crawl",
|
|
44
|
-
"spider",
|
|
45
|
-
"scanner",
|
|
46
|
-
"curl/",
|
|
47
|
-
"wget/",
|
|
48
|
-
"httpclient",
|
|
49
|
-
"python-requests",
|
|
50
|
-
"go-http-client",
|
|
51
|
-
"okhttp",
|
|
52
|
-
"java/",
|
|
53
|
-
"httrack",
|
|
54
|
-
"headlesschrome",
|
|
55
|
-
"phantomjs",
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
const INTERNAL_EMAIL_DOMAINS = ["nordsym.com", "apiclaw.cloud"];
|
|
59
|
-
const INTERNAL_EMAIL_EXACT = ["gustav@nordsym.com", "gustavnordsync@gmail.com"];
|
|
60
|
-
|
|
61
|
-
export function classifyLocalSource(input: {
|
|
62
|
-
userAgent?: string | null;
|
|
63
|
-
email?: string | null;
|
|
64
|
-
fingerprint?: string | null;
|
|
65
|
-
env?: NodeJS.ProcessEnv;
|
|
66
|
-
}): Classification {
|
|
67
|
-
const email = (input.email || "").toLowerCase().trim();
|
|
68
|
-
if (email) {
|
|
69
|
-
if (INTERNAL_EMAIL_EXACT.includes(email)) return "internal";
|
|
70
|
-
const domain = email.split("@")[1] || "";
|
|
71
|
-
if (INTERNAL_EMAIL_DOMAINS.includes(domain)) return "internal";
|
|
72
|
-
}
|
|
73
|
-
const env = input.env || process.env;
|
|
74
|
-
for (const key of CI_ENV_KEYS) {
|
|
75
|
-
const val = env[key];
|
|
76
|
-
if (val && val !== "false" && val !== "0") return "ci";
|
|
77
|
-
}
|
|
78
|
-
const ua = (input.userAgent || "").toLowerCase();
|
|
79
|
-
if (ua) {
|
|
80
|
-
for (const m of BOT_UA_MARKERS) {
|
|
81
|
-
if (ua.includes(m)) return "bot";
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return "human";
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Persistent first-run marker stored alongside the session file.
|
|
88
|
-
const MARKER_DIR = path.join(os.homedir(), ".apiclaw");
|
|
89
|
-
const MARKER_FILE = path.join(MARKER_DIR, "funnel-markers.json");
|
|
90
|
-
|
|
91
|
-
function readMarkers(): Record<string, number> {
|
|
92
|
-
try {
|
|
93
|
-
if (!fs.existsSync(MARKER_FILE)) return {};
|
|
94
|
-
return JSON.parse(fs.readFileSync(MARKER_FILE, "utf8")) || {};
|
|
95
|
-
} catch {
|
|
96
|
-
return {};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function writeMarkers(m: Record<string, number>): void {
|
|
101
|
-
try {
|
|
102
|
-
if (!fs.existsSync(MARKER_DIR)) fs.mkdirSync(MARKER_DIR, { mode: 0o700 });
|
|
103
|
-
fs.writeFileSync(MARKER_FILE, JSON.stringify(m), { mode: 0o600 });
|
|
104
|
-
} catch {
|
|
105
|
-
/* ignore */
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function hasLocalMarker(key: string): boolean {
|
|
110
|
-
return !!readMarkers()[key];
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function setLocalMarker(key: string): void {
|
|
114
|
-
const m = readMarkers();
|
|
115
|
-
m[key] = Date.now();
|
|
116
|
-
writeMarkers(m);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export interface EmitArgs {
|
|
120
|
-
event: FunnelEventName;
|
|
121
|
-
workspaceId?: string;
|
|
122
|
-
fingerprint?: string;
|
|
123
|
-
sessionToken?: string;
|
|
124
|
-
email?: string;
|
|
125
|
-
mcpClient?: string;
|
|
126
|
-
platform?: string;
|
|
127
|
-
version?: string;
|
|
128
|
-
dedupeKey?: string;
|
|
129
|
-
props?: Record<string, unknown>;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function emitFunnelEvent(args: EmitArgs): void {
|
|
133
|
-
if (process.env.APICLAW_TELEMETRY === "false") return;
|
|
134
|
-
|
|
135
|
-
const classification = classifyLocalSource({
|
|
136
|
-
email: args.email,
|
|
137
|
-
fingerprint: args.fingerprint,
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
const payload = {
|
|
141
|
-
path: "funnel:recordEvent",
|
|
142
|
-
args: {
|
|
143
|
-
event: args.event,
|
|
144
|
-
classification,
|
|
145
|
-
workspaceId: args.workspaceId,
|
|
146
|
-
fingerprint: args.fingerprint,
|
|
147
|
-
sessionToken: args.sessionToken,
|
|
148
|
-
email: args.email,
|
|
149
|
-
userAgent: `apiclaw-mcp/${args.version || "unknown"}`,
|
|
150
|
-
mcpClient: args.mcpClient,
|
|
151
|
-
platform: args.platform || process.platform,
|
|
152
|
-
version: args.version,
|
|
153
|
-
dedupeKey: args.dedupeKey,
|
|
154
|
-
props: args.props,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
// Fire and forget.
|
|
159
|
-
try {
|
|
160
|
-
fetch(`${CONVEX_URL}/api/mutation`, {
|
|
161
|
-
method: "POST",
|
|
162
|
-
headers: { "Content-Type": "application/json" },
|
|
163
|
-
body: JSON.stringify(payload),
|
|
164
|
-
}).catch(() => {});
|
|
165
|
-
} catch {
|
|
166
|
-
/* ignore */
|
|
167
|
-
}
|
|
168
|
-
}
|
package/src/funnel.test.ts
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for funnel classification + registration guard + event canon.
|
|
3
|
-
* Run: npx tsx src/funnel.test.ts
|
|
4
|
-
*/
|
|
5
|
-
import { strict as assert } from 'node:assert';
|
|
6
|
-
import { classifyLocalSource } from './funnel-client.js';
|
|
7
|
-
import {
|
|
8
|
-
requireVerifiedOwner,
|
|
9
|
-
FREE_CALL_PATHS,
|
|
10
|
-
ENFORCED_CALL_PATHS,
|
|
11
|
-
type WorkspaceContextLike,
|
|
12
|
-
} from './registration-guard.js';
|
|
13
|
-
|
|
14
|
-
let failed = 0;
|
|
15
|
-
let passed = 0;
|
|
16
|
-
|
|
17
|
-
function test(name: string, fn: () => void) {
|
|
18
|
-
try {
|
|
19
|
-
fn();
|
|
20
|
-
console.log(`✅ ${name}`);
|
|
21
|
-
passed++;
|
|
22
|
-
} catch (e: any) {
|
|
23
|
-
console.log(`❌ ${name}`);
|
|
24
|
-
console.log(` ${e.message || e}`);
|
|
25
|
-
failed++;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ------------------------------------------------------------------
|
|
30
|
-
// Classification
|
|
31
|
-
// ------------------------------------------------------------------
|
|
32
|
-
test('classify: human is default', () => {
|
|
33
|
-
assert.equal(classifyLocalSource({ env: {} }), 'human');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test('classify: CI env flag → ci', () => {
|
|
37
|
-
assert.equal(classifyLocalSource({ env: { CI: 'true' } }), 'ci');
|
|
38
|
-
assert.equal(classifyLocalSource({ env: { GITHUB_ACTIONS: '1' } }), 'ci');
|
|
39
|
-
assert.equal(classifyLocalSource({ env: { CIRCLECI: 'true' } }), 'ci');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('classify: CI=false is NOT ci', () => {
|
|
43
|
-
assert.equal(classifyLocalSource({ env: { CI: 'false' } }), 'human');
|
|
44
|
-
assert.equal(classifyLocalSource({ env: { CI: '0' } }), 'human');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('classify: bot UA → bot', () => {
|
|
48
|
-
assert.equal(classifyLocalSource({ env: {}, userAgent: 'curl/7.88' }), 'bot');
|
|
49
|
-
assert.equal(
|
|
50
|
-
classifyLocalSource({ env: {}, userAgent: 'Mozilla/5.0 (compatible; Googlebot/2.1)' }),
|
|
51
|
-
'bot'
|
|
52
|
-
);
|
|
53
|
-
assert.equal(classifyLocalSource({ env: {}, userAgent: 'python-requests/2.31' }), 'bot');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test('classify: internal email domain → internal', () => {
|
|
57
|
-
assert.equal(
|
|
58
|
-
classifyLocalSource({ env: {}, email: 'someone@nordsym.com' }),
|
|
59
|
-
'internal'
|
|
60
|
-
);
|
|
61
|
-
assert.equal(
|
|
62
|
-
classifyLocalSource({ env: {}, email: 'test@apiclaw.cloud' }),
|
|
63
|
-
'internal'
|
|
64
|
-
);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test('classify: internal exact email → internal (even with CI set)', () => {
|
|
68
|
-
assert.equal(
|
|
69
|
-
classifyLocalSource({
|
|
70
|
-
env: { CI: 'true' },
|
|
71
|
-
email: 'gustavnordsync@gmail.com',
|
|
72
|
-
}),
|
|
73
|
-
'internal'
|
|
74
|
-
);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('classify: precedence internal > ci > bot > human', () => {
|
|
78
|
-
// internal wins over CI
|
|
79
|
-
assert.equal(
|
|
80
|
-
classifyLocalSource({ env: { CI: 'true' }, email: 'x@nordsym.com' }),
|
|
81
|
-
'internal'
|
|
82
|
-
);
|
|
83
|
-
// ci wins over bot
|
|
84
|
-
assert.equal(
|
|
85
|
-
classifyLocalSource({ env: { CI: 'true' }, userAgent: 'curl/8' }),
|
|
86
|
-
'ci'
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// ------------------------------------------------------------------
|
|
91
|
-
// requireVerifiedOwner
|
|
92
|
-
// ------------------------------------------------------------------
|
|
93
|
-
const good: WorkspaceContextLike = {
|
|
94
|
-
sessionToken: 'tok',
|
|
95
|
-
workspaceId: 'ws_1',
|
|
96
|
-
email: 'user@example.com',
|
|
97
|
-
tier: 'free',
|
|
98
|
-
status: 'active',
|
|
99
|
-
usageRemaining: 42,
|
|
100
|
-
usageCount: 8,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
test('guard: no workspace → no_session', () => {
|
|
104
|
-
const r = requireVerifiedOwner(null);
|
|
105
|
-
assert.equal(r.ok, false);
|
|
106
|
-
if (!r.ok) assert.equal(r.reason, 'no_session');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('guard: workspace without email → pending_verification', () => {
|
|
110
|
-
const r = requireVerifiedOwner({ ...good, email: '' });
|
|
111
|
-
assert.equal(r.ok, false);
|
|
112
|
-
if (!r.ok) assert.equal(r.reason, 'pending_verification');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('guard: workspace status pending → not_verified', () => {
|
|
116
|
-
const r = requireVerifiedOwner({ ...good, status: 'pending' });
|
|
117
|
-
assert.equal(r.ok, false);
|
|
118
|
-
if (!r.ok) assert.equal(r.reason, 'not_verified');
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('guard: quota exhausted → quota_exceeded', () => {
|
|
122
|
-
const r = requireVerifiedOwner({ ...good, usageRemaining: 0 });
|
|
123
|
-
assert.equal(r.ok, false);
|
|
124
|
-
if (!r.ok) assert.equal(r.reason, 'quota_exceeded');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test('guard: happy path → ok', () => {
|
|
128
|
-
const r = requireVerifiedOwner(good);
|
|
129
|
-
assert.equal(r.ok, true);
|
|
130
|
-
if (r.ok) assert.equal(r.ctx.email, 'user@example.com');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test('guard: unlimited (usageRemaining=-1) → ok', () => {
|
|
134
|
-
const r = requireVerifiedOwner({ ...good, usageRemaining: -1 });
|
|
135
|
-
assert.equal(r.ok, true);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// ------------------------------------------------------------------
|
|
139
|
-
// Enforcement matrix coverage
|
|
140
|
-
// ------------------------------------------------------------------
|
|
141
|
-
test('matrix: discover_apis is free', () => {
|
|
142
|
-
assert.equal(FREE_CALL_PATHS.has('discover_apis'), true);
|
|
143
|
-
assert.equal(ENFORCED_CALL_PATHS.has('discover_apis'), false);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
test('matrix: call_api / capability / resume_chain are enforced', () => {
|
|
147
|
-
for (const p of ['call_api', 'capability', 'resume_chain']) {
|
|
148
|
-
assert.equal(ENFORCED_CALL_PATHS.has(p), true, `${p} should be enforced`);
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test('matrix: register_owner + verify_code are free (they BECOME the auth)', () => {
|
|
153
|
-
assert.equal(FREE_CALL_PATHS.has('register_owner'), true);
|
|
154
|
-
assert.equal(FREE_CALL_PATHS.has('verify_code'), true);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test('matrix: free and enforced sets are disjoint', () => {
|
|
158
|
-
for (const p of FREE_CALL_PATHS) {
|
|
159
|
-
assert.equal(ENFORCED_CALL_PATHS.has(p), false, `${p} is in both sets`);
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// ------------------------------------------------------------------
|
|
164
|
-
// Event canon type surface
|
|
165
|
-
// ------------------------------------------------------------------
|
|
166
|
-
import type { FunnelEventName } from './funnel-client.js';
|
|
167
|
-
|
|
168
|
-
test('event canon: all 11 approved events are typed', () => {
|
|
169
|
-
const names: FunnelEventName[] = [
|
|
170
|
-
'install',
|
|
171
|
-
'first_run',
|
|
172
|
-
'register_owner',
|
|
173
|
-
'verify_code',
|
|
174
|
-
'first_call_api_success',
|
|
175
|
-
'register_owner_failed',
|
|
176
|
-
'verify_code_failed',
|
|
177
|
-
'call_api_blocked',
|
|
178
|
-
'call_api_error',
|
|
179
|
-
'quota_hit',
|
|
180
|
-
'gateway_retry',
|
|
181
|
-
];
|
|
182
|
-
assert.equal(names.length, 11);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
console.log('');
|
|
186
|
-
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
187
|
-
if (failed > 0) process.exit(1);
|
package/src/gateway-client.ts
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* APIClaw Gateway Client - Thin HTTP client for the Intelligent Gateway
|
|
3
|
-
*
|
|
4
|
-
* Routes all API execution through POST /v1/execute on Convex,
|
|
5
|
-
* replacing local executeOpenAPI / executeMetered calls.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync } from 'fs';
|
|
9
|
-
import { join } from 'path';
|
|
10
|
-
import { homedir } from 'os';
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Types
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
export interface GatewayResponse {
|
|
17
|
-
success: boolean;
|
|
18
|
-
provider: string;
|
|
19
|
-
action: string;
|
|
20
|
-
data?: any;
|
|
21
|
-
error?: string;
|
|
22
|
-
cost?: number;
|
|
23
|
-
_apiclaw?: {
|
|
24
|
-
latencyMs: number;
|
|
25
|
-
route: string;
|
|
26
|
-
gateway: boolean;
|
|
27
|
-
model?: string;
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface GatewayExecuteOptions {
|
|
32
|
-
workspaceId?: string;
|
|
33
|
-
stream?: boolean;
|
|
34
|
-
routeOverride?: string;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// Secret resolution
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
function resolveInternalSecret(): string {
|
|
42
|
-
// 1. Environment variable
|
|
43
|
-
const envSecret = process.env.APICLAW_INTERNAL_SECRET;
|
|
44
|
-
if (envSecret) return envSecret;
|
|
45
|
-
|
|
46
|
-
// 2. File on disk
|
|
47
|
-
try {
|
|
48
|
-
const secretPath = join(homedir(), '.secrets', 'apiclaw-internal-secret');
|
|
49
|
-
return readFileSync(secretPath, 'utf-8').trim();
|
|
50
|
-
} catch {
|
|
51
|
-
// File doesn't exist or unreadable
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// 3. Fallback (will fail auth, which is correct)
|
|
55
|
-
return '';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
// Feature flag
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Returns true if gateway routing is enabled.
|
|
64
|
-
* Default: enabled. Set APICLAW_USE_GATEWAY=false to disable.
|
|
65
|
-
*/
|
|
66
|
-
export function isGatewayEnabled(): boolean {
|
|
67
|
-
const flag = process.env.APICLAW_USE_GATEWAY;
|
|
68
|
-
if (flag === undefined || flag === '') return true; // default on
|
|
69
|
-
return flag.toLowerCase() !== 'false';
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
// Client
|
|
74
|
-
// ---------------------------------------------------------------------------
|
|
75
|
-
|
|
76
|
-
const GATEWAY_BASE = 'https://adventurous-avocet-799.convex.site';
|
|
77
|
-
const GATEWAY_TIMEOUT_MS = 30_000;
|
|
78
|
-
|
|
79
|
-
export class GatewayClient {
|
|
80
|
-
private baseUrl: string;
|
|
81
|
-
private internalSecret: string;
|
|
82
|
-
|
|
83
|
-
constructor() {
|
|
84
|
-
this.baseUrl = GATEWAY_BASE;
|
|
85
|
-
this.internalSecret = resolveInternalSecret();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async execute(
|
|
89
|
-
provider: string,
|
|
90
|
-
action: string,
|
|
91
|
-
params: Record<string, any>,
|
|
92
|
-
options?: GatewayExecuteOptions,
|
|
93
|
-
): Promise<GatewayResponse> {
|
|
94
|
-
const url = `${this.baseUrl}/v1/execute`;
|
|
95
|
-
|
|
96
|
-
const headers: Record<string, string> = {
|
|
97
|
-
'Content-Type': 'application/json',
|
|
98
|
-
'X-APIClaw-Internal': this.internalSecret,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (options?.workspaceId) {
|
|
102
|
-
headers['X-APIClaw-Workspace'] = options.workspaceId;
|
|
103
|
-
}
|
|
104
|
-
if (options?.routeOverride) {
|
|
105
|
-
headers['X-APIClaw-Route'] = options.routeOverride;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const body = JSON.stringify({ provider, action, params });
|
|
109
|
-
|
|
110
|
-
// First attempt
|
|
111
|
-
let response = await this.doFetch(url, headers, body);
|
|
112
|
-
|
|
113
|
-
// Retry once on network error
|
|
114
|
-
if (!response) {
|
|
115
|
-
response = await this.doFetch(url, headers, body);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (!response) {
|
|
119
|
-
return {
|
|
120
|
-
success: false,
|
|
121
|
-
provider,
|
|
122
|
-
action,
|
|
123
|
-
error: 'Gateway unreachable after retry',
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
const json = await response.json() as any;
|
|
129
|
-
|
|
130
|
-
if (!response.ok) {
|
|
131
|
-
return {
|
|
132
|
-
success: false,
|
|
133
|
-
provider: json.provider || provider,
|
|
134
|
-
action: json.action || action,
|
|
135
|
-
error: json.error || `Gateway HTTP ${response.status}`,
|
|
136
|
-
_apiclaw: json._apiclaw,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
success: json.success ?? true,
|
|
142
|
-
provider: json.provider || provider,
|
|
143
|
-
action: json.action || action,
|
|
144
|
-
data: json.data,
|
|
145
|
-
error: json.error,
|
|
146
|
-
cost: json.cost,
|
|
147
|
-
_apiclaw: json._apiclaw,
|
|
148
|
-
};
|
|
149
|
-
} catch (e: any) {
|
|
150
|
-
return {
|
|
151
|
-
success: false,
|
|
152
|
-
provider,
|
|
153
|
-
action,
|
|
154
|
-
error: `Gateway response parse error: ${e.message}`,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Perform a single fetch with timeout. Returns null on network error.
|
|
161
|
-
*/
|
|
162
|
-
private async doFetch(
|
|
163
|
-
url: string,
|
|
164
|
-
headers: Record<string, string>,
|
|
165
|
-
body: string,
|
|
166
|
-
): Promise<Response | null> {
|
|
167
|
-
try {
|
|
168
|
-
const controller = new AbortController();
|
|
169
|
-
const timer = setTimeout(() => controller.abort(), GATEWAY_TIMEOUT_MS);
|
|
170
|
-
|
|
171
|
-
const response = await fetch(url, {
|
|
172
|
-
method: 'POST',
|
|
173
|
-
headers,
|
|
174
|
-
body,
|
|
175
|
-
signal: controller.signal,
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
clearTimeout(timer);
|
|
179
|
-
return response;
|
|
180
|
-
} catch {
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Singleton
|
|
187
|
-
let _gateway: GatewayClient | null = null;
|
|
188
|
-
|
|
189
|
-
export function getGateway(): GatewayClient {
|
|
190
|
-
if (!_gateway) _gateway = new GatewayClient();
|
|
191
|
-
return _gateway;
|
|
192
|
-
}
|
package/src/hivr-whitelist.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hivr Bees Auto-Whitelist
|
|
3
|
-
* Dynamically fetches active agents from Hivr's Convex deployment
|
|
4
|
-
* Falls back to static whitelist if Convex is unreachable
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Hivr PROD Convex deployment
|
|
8
|
-
const HIVR_CONVEX_URL = "https://brilliant-puffin-712.eu-west-1.convex.cloud";
|
|
9
|
-
|
|
10
|
-
// Fallback static whitelist (in case Convex is down)
|
|
11
|
-
const STATIC_WHITELIST = [
|
|
12
|
-
'bytebee',
|
|
13
|
-
'analyzerbee',
|
|
14
|
-
'buildbee',
|
|
15
|
-
'buzzwriter',
|
|
16
|
-
'hivemind',
|
|
17
|
-
'hivesage',
|
|
18
|
-
'symbot',
|
|
19
|
-
'hivrqueen',
|
|
20
|
-
'marketmaven',
|
|
21
|
-
'reconbee',
|
|
22
|
-
'sprintbee',
|
|
23
|
-
'quillbee',
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
// Cache whitelist for 5 minutes
|
|
27
|
-
let cachedWhitelist: string[] | null = null;
|
|
28
|
-
let cacheExpiry: number = 0;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Fetch all active agents from Hivr Convex
|
|
32
|
-
*/
|
|
33
|
-
async function fetchHivrAgents(): Promise<string[]> {
|
|
34
|
-
try {
|
|
35
|
-
// Call Convex HTTP API directly
|
|
36
|
-
const response = await fetch(`${HIVR_CONVEX_URL}/api/query`, {
|
|
37
|
-
method: 'POST',
|
|
38
|
-
headers: {
|
|
39
|
-
'Content-Type': 'application/json',
|
|
40
|
-
},
|
|
41
|
-
body: JSON.stringify({
|
|
42
|
-
path: 'agents:list',
|
|
43
|
-
args: {},
|
|
44
|
-
}),
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
if (!response.ok) {
|
|
48
|
-
console.warn('[Hivr Whitelist] Convex HTTP API error, using static whitelist');
|
|
49
|
-
return STATIC_WHITELIST;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const agents = await response.json() as any[];
|
|
53
|
-
|
|
54
|
-
if (!agents || !Array.isArray(agents)) {
|
|
55
|
-
console.warn('[Hivr Whitelist] Invalid response from Hivr Convex, using static whitelist');
|
|
56
|
-
return STATIC_WHITELIST;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Extract handles (Hivr uses 'handle', not 'agentId')
|
|
60
|
-
const handles = agents
|
|
61
|
-
.map((a: any) => a.handle?.toLowerCase().trim())
|
|
62
|
-
.filter((h: string | undefined) => h && h.length > 0);
|
|
63
|
-
|
|
64
|
-
console.log(`[Hivr Whitelist] Fetched ${handles.length} agents from Hivr`);
|
|
65
|
-
return handles;
|
|
66
|
-
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.error('[Hivr Whitelist] Failed to fetch from Hivr Convex:', error);
|
|
69
|
-
return STATIC_WHITELIST;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Get current whitelist (cached or fresh)
|
|
75
|
-
*/
|
|
76
|
-
export async function getWhitelist(): Promise<string[]> {
|
|
77
|
-
const now = Date.now();
|
|
78
|
-
|
|
79
|
-
// Return cached if still valid
|
|
80
|
-
if (cachedWhitelist && now < cacheExpiry) {
|
|
81
|
-
return cachedWhitelist;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Fetch fresh whitelist
|
|
85
|
-
cachedWhitelist = await fetchHivrAgents();
|
|
86
|
-
cacheExpiry = now + (5 * 60 * 1000); // 5 minutes
|
|
87
|
-
|
|
88
|
-
return cachedWhitelist;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Check if agent is authorized
|
|
93
|
-
*/
|
|
94
|
-
export async function isAuthorized(agentId: string | undefined): Promise<boolean> {
|
|
95
|
-
if (!agentId) return false;
|
|
96
|
-
|
|
97
|
-
const whitelist = await getWhitelist();
|
|
98
|
-
const normalized = agentId.toLowerCase().trim();
|
|
99
|
-
|
|
100
|
-
return whitelist.includes(normalized);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Force refresh whitelist (call after adding new bee)
|
|
105
|
-
*/
|
|
106
|
-
export function invalidateCache(): void {
|
|
107
|
-
cachedWhitelist = null;
|
|
108
|
-
cacheExpiry = 0;
|
|
109
|
-
console.log('[Hivr Whitelist] Cache invalidated');
|
|
110
|
-
}
|