@nordsym/apiclaw 2.1.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/funnel-client.d.ts +24 -0
- package/dist/funnel-client.d.ts.map +1 -0
- package/dist/funnel-client.js +131 -0
- package/dist/funnel-client.js.map +1 -0
- package/dist/funnel.test.d.ts +2 -0
- package/dist/funnel.test.d.ts.map +1 -0
- package/dist/funnel.test.js +145 -0
- package/dist/funnel.test.js.map +1 -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 +161 -74
- package/dist/index.js.map +1 -1
- package/dist/postinstall.d.ts +0 -5
- package/dist/postinstall.d.ts.map +1 -1
- package/dist/postinstall.js +24 -3
- package/dist/postinstall.js.map +1 -1
- package/dist/registration-guard.d.ts +29 -0
- package/dist/registration-guard.d.ts.map +1 -0
- package/dist/registration-guard.js +87 -0
- package/dist/registration-guard.js.map +1 -0
- package/package.json +7 -2
- package/.claude/settings.local.json +0 -9
- package/.env.prod +0 -1
- package/apiclaw-README.md +0 -494
- package/convex/_generated/api.d.ts +0 -137
- 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/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 -50
- 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/http.ts +0 -3430
- 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/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 -869
- 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/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 -2611
- 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 -18
- package/src/product-whitelist.ts +0 -246
- package/src/proxy.ts +0 -36
- 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
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* APIClaw CLI Login
|
|
3
|
-
* In-terminal email signup/login via magic link.
|
|
4
|
-
* Zero browser required.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { input } from '@inquirer/prompts';
|
|
8
|
-
import ora from 'ora';
|
|
9
|
-
import chalk from 'chalk';
|
|
10
|
-
import { writeSession, readSession, getMachineFingerprint } from '../../session.js';
|
|
11
|
-
|
|
12
|
-
const CONVEX_URL = 'https://adventurous-avocet-799.convex.cloud';
|
|
13
|
-
const APICLAW_URL = 'https://apiclaw.cloud';
|
|
14
|
-
const POLL_INTERVAL_MS = 2000;
|
|
15
|
-
const MAX_WAIT_MS = 15 * 60 * 1000; // 15 min (magic link TTL)
|
|
16
|
-
|
|
17
|
-
export interface LoginOptions {
|
|
18
|
-
email?: string;
|
|
19
|
-
force?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Request a magic link via the CLI-specific APIClaw endpoint.
|
|
24
|
-
* Returns the token directly so we can poll for verification.
|
|
25
|
-
*/
|
|
26
|
-
async function requestMagicLink(email: string): Promise<string> {
|
|
27
|
-
const fingerprint = getMachineFingerprint();
|
|
28
|
-
|
|
29
|
-
const res = await fetch(`${APICLAW_URL}/api/workspace-auth/cli-link`, {
|
|
30
|
-
method: 'POST',
|
|
31
|
-
headers: { 'Content-Type': 'application/json' },
|
|
32
|
-
body: JSON.stringify({ email: email.toLowerCase().trim(), fingerprint }),
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
if (!res.ok) {
|
|
36
|
-
const err = await res.json().catch(() => ({}));
|
|
37
|
-
throw new Error((err as any).error || `Failed to send magic link (${res.status})`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const data = await res.json().catch(() => ({} as Record<string, unknown>));
|
|
41
|
-
const token = (data as Record<string, unknown>)?.token as string | undefined;
|
|
42
|
-
|
|
43
|
-
if (!token) {
|
|
44
|
-
throw new Error('No token returned. Try again.');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return token;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Poll Convex until the magic link is clicked or expired
|
|
52
|
-
*/
|
|
53
|
-
async function pollForVerification(token: string): Promise<{
|
|
54
|
-
sessionToken: string;
|
|
55
|
-
workspaceId: string;
|
|
56
|
-
email: string;
|
|
57
|
-
tier: string;
|
|
58
|
-
isNew: boolean;
|
|
59
|
-
}> {
|
|
60
|
-
const started = Date.now();
|
|
61
|
-
|
|
62
|
-
while (Date.now() - started < MAX_WAIT_MS) {
|
|
63
|
-
await sleep(POLL_INTERVAL_MS);
|
|
64
|
-
|
|
65
|
-
const res = await fetch(`${CONVEX_URL}/api/query`, {
|
|
66
|
-
method: 'POST',
|
|
67
|
-
headers: { 'Content-Type': 'application/json' },
|
|
68
|
-
body: JSON.stringify({
|
|
69
|
-
path: 'workspaces:pollMagicLink',
|
|
70
|
-
args: { token },
|
|
71
|
-
}),
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const data = await res.json().catch(() => ({}));
|
|
75
|
-
interface PollResult {
|
|
76
|
-
status: string;
|
|
77
|
-
sessionToken?: string;
|
|
78
|
-
workspace?: { id: string; email: string; tier: string; usageCount: number };
|
|
79
|
-
expiresAt?: number;
|
|
80
|
-
}
|
|
81
|
-
const raw = (data as Record<string, unknown>)?.value || data;
|
|
82
|
-
const result = raw as PollResult;
|
|
83
|
-
|
|
84
|
-
if (result?.status === 'verified' && result.sessionToken && result.workspace) {
|
|
85
|
-
return {
|
|
86
|
-
sessionToken: result.sessionToken,
|
|
87
|
-
workspaceId: result.workspace.id,
|
|
88
|
-
email: result.workspace.email,
|
|
89
|
-
tier: result.workspace.tier,
|
|
90
|
-
isNew: result.workspace.usageCount === 0,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (result?.status === 'expired') {
|
|
95
|
-
throw new Error('Magic link expired. Run login again to get a fresh link.');
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
throw new Error('Timed out waiting for email verification.');
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function sleep(ms: number): Promise<void> {
|
|
103
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Main login command
|
|
108
|
-
*/
|
|
109
|
-
export async function loginCommand(options: LoginOptions = {}): Promise<{
|
|
110
|
-
sessionToken: string;
|
|
111
|
-
workspaceId: string;
|
|
112
|
-
email: string;
|
|
113
|
-
tier: string;
|
|
114
|
-
isNew: boolean;
|
|
115
|
-
} | null> {
|
|
116
|
-
// Check existing session
|
|
117
|
-
if (!options.force) {
|
|
118
|
-
const existing = readSession();
|
|
119
|
-
if (existing) {
|
|
120
|
-
console.log(chalk.green(`\nā Already signed in as ${chalk.bold(existing.email)}\n`));
|
|
121
|
-
console.log(
|
|
122
|
-
chalk.dim(` Use ${chalk.white('npx @nordsym/apiclaw login --force')} to switch accounts.\n`)
|
|
123
|
-
);
|
|
124
|
-
return {
|
|
125
|
-
sessionToken: existing.sessionToken,
|
|
126
|
-
workspaceId: existing.workspaceId,
|
|
127
|
-
email: existing.email,
|
|
128
|
-
tier: 'free',
|
|
129
|
-
isNew: false,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
console.log('');
|
|
135
|
-
console.log(chalk.bold(' š¦ APIClaw Workspace'));
|
|
136
|
-
console.log(chalk.dim(' Sign in or create a free account\n'));
|
|
137
|
-
|
|
138
|
-
// Get email
|
|
139
|
-
let email = options.email;
|
|
140
|
-
if (!email) {
|
|
141
|
-
email = await input({
|
|
142
|
-
message: 'Your email:',
|
|
143
|
-
validate: (val) => {
|
|
144
|
-
if (!val || !val.includes('@')) return 'Enter a valid email address';
|
|
145
|
-
return true;
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
email = email.trim().toLowerCase();
|
|
151
|
-
|
|
152
|
-
// Send magic link
|
|
153
|
-
const sendSpinner = ora('Sending magic link...').start();
|
|
154
|
-
let token: string;
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
token = await requestMagicLink(email);
|
|
158
|
-
sendSpinner.succeed(`Magic link sent to ${chalk.bold(email)}`);
|
|
159
|
-
} catch (err: any) {
|
|
160
|
-
sendSpinner.fail(`Failed: ${err.message}`);
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Tell user to check email
|
|
165
|
-
console.log('');
|
|
166
|
-
console.log(chalk.cyan(' ā Check your inbox and click the link to continue.'));
|
|
167
|
-
console.log(chalk.dim(' The link expires in 15 minutes.\n'));
|
|
168
|
-
|
|
169
|
-
// Poll for verification
|
|
170
|
-
const pollSpinner = ora('Waiting for email verification...').start();
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
const result = await pollForVerification(token);
|
|
174
|
-
|
|
175
|
-
// Save session locally
|
|
176
|
-
writeSession(result.sessionToken, result.workspaceId, result.email);
|
|
177
|
-
|
|
178
|
-
pollSpinner.succeed(
|
|
179
|
-
result.isNew
|
|
180
|
-
? chalk.green(`Workspace created for ${chalk.bold(result.email)}`)
|
|
181
|
-
: chalk.green(`Signed in as ${chalk.bold(result.email)}`)
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
console.log('');
|
|
185
|
-
|
|
186
|
-
if (result.isNew) {
|
|
187
|
-
console.log(chalk.bold(' ā
You\'re in. Free tier: 50 API calls/month.'));
|
|
188
|
-
console.log(
|
|
189
|
-
chalk.dim(
|
|
190
|
-
` Dashboard: ${chalk.white(`${APICLAW_URL}/workspace`)}\n`
|
|
191
|
-
)
|
|
192
|
-
);
|
|
193
|
-
} else {
|
|
194
|
-
console.log(chalk.bold(` ā
Welcome back. Tier: ${result.tier}.`));
|
|
195
|
-
console.log('');
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return result;
|
|
199
|
-
} catch (err: any) {
|
|
200
|
-
pollSpinner.fail(err.message);
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Install Command
|
|
3
|
-
* Simple, focused command to install APIClaw into MCP config files
|
|
4
|
-
* Supports Claude Desktop and Claude Code
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
8
|
-
import { dirname, join } from 'path';
|
|
9
|
-
import { platform, homedir } from 'os';
|
|
10
|
-
import { execSync } from 'child_process';
|
|
11
|
-
import chalk from 'chalk';
|
|
12
|
-
|
|
13
|
-
export interface MCPInstallOptions {
|
|
14
|
-
client?: string;
|
|
15
|
-
dryRun?: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
type Platform = 'mac' | 'win' | 'linux';
|
|
19
|
-
|
|
20
|
-
interface ClientConfig {
|
|
21
|
-
name: string;
|
|
22
|
-
displayName: string;
|
|
23
|
-
getConfigPath: () => string;
|
|
24
|
-
configKey: string; // Key path in config (e.g., "mcpServers" or root level)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Detect operating system
|
|
29
|
-
*/
|
|
30
|
-
function detectOS(): Platform {
|
|
31
|
-
const os = platform();
|
|
32
|
-
switch (os) {
|
|
33
|
-
case 'darwin': return 'mac';
|
|
34
|
-
case 'win32': return 'win';
|
|
35
|
-
default: return 'linux';
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get home directory
|
|
41
|
-
*/
|
|
42
|
-
function getHome(): string {
|
|
43
|
-
return homedir();
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Get config paths for supported clients
|
|
48
|
-
*/
|
|
49
|
-
function getClientConfigs(): ClientConfig[] {
|
|
50
|
-
const os = detectOS();
|
|
51
|
-
const home = getHome();
|
|
52
|
-
|
|
53
|
-
const clients: ClientConfig[] = [
|
|
54
|
-
{
|
|
55
|
-
name: 'claude-desktop',
|
|
56
|
-
displayName: 'Claude Desktop',
|
|
57
|
-
configKey: 'mcpServers',
|
|
58
|
-
getConfigPath: () => {
|
|
59
|
-
switch (os) {
|
|
60
|
-
case 'mac':
|
|
61
|
-
return join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
62
|
-
case 'win':
|
|
63
|
-
return join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
|
|
64
|
-
case 'linux':
|
|
65
|
-
return join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: 'claude-code',
|
|
71
|
-
displayName: 'Claude Code',
|
|
72
|
-
configKey: 'mcpServers',
|
|
73
|
-
getConfigPath: () => {
|
|
74
|
-
// Claude Code uses ~/.claude.json on all platforms
|
|
75
|
-
return join(home, '.claude.json');
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: 'codex',
|
|
80
|
-
displayName: 'Codex (OpenAI)',
|
|
81
|
-
configKey: 'mcp',
|
|
82
|
-
getConfigPath: () => {
|
|
83
|
-
// Codex uses ~/.codex/config.toml
|
|
84
|
-
return join(home, '.codex', 'config.toml');
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
return clients;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* APIClaw MCP server configuration
|
|
94
|
-
*/
|
|
95
|
-
const APICLAW_CONFIG = {
|
|
96
|
-
command: 'npx',
|
|
97
|
-
args: ['-y', '@nordsym/apiclaw', 'serve'],
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Read JSON config file
|
|
102
|
-
*/
|
|
103
|
-
function readConfig(path: string): { success: boolean; config: any; error?: string; isNew?: boolean } {
|
|
104
|
-
try {
|
|
105
|
-
if (!existsSync(path)) {
|
|
106
|
-
return { success: true, config: {}, isNew: true };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const content = readFileSync(path, 'utf-8');
|
|
110
|
-
if (!content.trim()) {
|
|
111
|
-
return { success: true, config: {}, isNew: true };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return { success: true, config: JSON.parse(content), isNew: false };
|
|
115
|
-
} catch (error) {
|
|
116
|
-
return {
|
|
117
|
-
success: false,
|
|
118
|
-
config: null,
|
|
119
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Write JSON config file with backup
|
|
126
|
-
*/
|
|
127
|
-
function writeConfig(path: string, config: any, createBackup = true): { success: boolean; error?: string } {
|
|
128
|
-
try {
|
|
129
|
-
const dir = dirname(path);
|
|
130
|
-
if (!existsSync(dir)) {
|
|
131
|
-
mkdirSync(dir, { recursive: true });
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Create backup if file exists
|
|
135
|
-
if (createBackup && existsSync(path)) {
|
|
136
|
-
const backupPath = `${path}.backup.${Date.now()}.json`;
|
|
137
|
-
const existing = readFileSync(path, 'utf-8');
|
|
138
|
-
writeFileSync(backupPath, existing, 'utf-8');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
writeFileSync(path, JSON.stringify(config, null, 2), 'utf-8');
|
|
142
|
-
return { success: true };
|
|
143
|
-
} catch (error) {
|
|
144
|
-
return {
|
|
145
|
-
success: false,
|
|
146
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Check if Codex CLI is available
|
|
153
|
-
*/
|
|
154
|
-
function isCodexAvailable(): boolean {
|
|
155
|
-
try {
|
|
156
|
-
execSync('codex --version', { stdio: 'pipe' });
|
|
157
|
-
return true;
|
|
158
|
-
} catch {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Install APIClaw to Codex using CLI
|
|
165
|
-
*/
|
|
166
|
-
function installToCodex(dryRun: boolean): { success: boolean; message: string; skipped?: boolean } {
|
|
167
|
-
if (!isCodexAvailable()) {
|
|
168
|
-
return { success: false, message: 'Codex CLI not found' };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
// Check if already installed
|
|
173
|
-
try {
|
|
174
|
-
const output = execSync('codex mcp get apiclaw', { encoding: 'utf-8', stdio: 'pipe' });
|
|
175
|
-
if (output.includes('apiclaw')) {
|
|
176
|
-
return { success: true, message: 'Already installed', skipped: true };
|
|
177
|
-
}
|
|
178
|
-
} catch {
|
|
179
|
-
// Not installed, continue
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (dryRun) {
|
|
183
|
-
console.log(chalk.cyan('\n Would run: codex mcp add apiclaw -- npx -y @nordsym/apiclaw'));
|
|
184
|
-
return { success: true, message: 'Dry run - no changes made', skipped: true };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Install
|
|
188
|
-
execSync('codex mcp add apiclaw -- npx -y @nordsym/apiclaw', { stdio: 'pipe' });
|
|
189
|
-
return { success: true, message: 'Installed via CLI' };
|
|
190
|
-
} catch (error) {
|
|
191
|
-
return {
|
|
192
|
-
success: false,
|
|
193
|
-
message: error instanceof Error ? error.message : 'Unknown error'
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Install APIClaw into a client config
|
|
200
|
-
*/
|
|
201
|
-
function installToClient(client: ClientConfig, dryRun: boolean): { success: boolean; message: string; skipped?: boolean } {
|
|
202
|
-
// Special handling for Codex
|
|
203
|
-
if (client.name === 'codex') {
|
|
204
|
-
return installToCodex(dryRun);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const configPath = client.getConfigPath();
|
|
208
|
-
|
|
209
|
-
// Read existing config
|
|
210
|
-
const readResult = readConfig(configPath);
|
|
211
|
-
if (!readResult.success) {
|
|
212
|
-
return { success: false, message: `Failed to read config: ${readResult.error}` };
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const config = readResult.config;
|
|
216
|
-
|
|
217
|
-
// Initialize mcpServers if not present
|
|
218
|
-
if (!config.mcpServers) {
|
|
219
|
-
config.mcpServers = {};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Check if already installed
|
|
223
|
-
if (config.mcpServers.apiclaw) {
|
|
224
|
-
return { success: true, message: 'Already installed', skipped: true };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Add APIClaw config
|
|
228
|
-
config.mcpServers.apiclaw = APICLAW_CONFIG;
|
|
229
|
-
|
|
230
|
-
if (dryRun) {
|
|
231
|
-
console.log(chalk.cyan(`\n Would add to ${configPath}:`));
|
|
232
|
-
console.log(chalk.gray(JSON.stringify({ apiclaw: APICLAW_CONFIG }, null, 4)));
|
|
233
|
-
return { success: true, message: 'Dry run - no changes made', skipped: true };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Write config
|
|
237
|
-
const writeResult = writeConfig(configPath, config);
|
|
238
|
-
if (!writeResult.success) {
|
|
239
|
-
return { success: false, message: `Failed to write config: ${writeResult.error}` };
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return {
|
|
243
|
-
success: true,
|
|
244
|
-
message: readResult.isNew ? 'Created new config' : 'Updated config'
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Main mcp-install command handler
|
|
250
|
-
*/
|
|
251
|
-
export async function mcpInstallCommand(options: MCPInstallOptions): Promise<void> {
|
|
252
|
-
const os = detectOS();
|
|
253
|
-
const osName = os === 'mac' ? 'macOS' : os === 'win' ? 'Windows' : 'Linux';
|
|
254
|
-
|
|
255
|
-
console.log(chalk.bold('\nš¦ APIClaw MCP Install\n'));
|
|
256
|
-
console.log(`Platform: ${osName}\n`);
|
|
257
|
-
|
|
258
|
-
const clients = getClientConfigs();
|
|
259
|
-
let targetClients = clients;
|
|
260
|
-
|
|
261
|
-
// Filter to specific client if requested
|
|
262
|
-
if (options.client) {
|
|
263
|
-
const normalizedClient = options.client.toLowerCase().replace(/[_\s]/g, '-');
|
|
264
|
-
const aliases: Record<string, string> = {
|
|
265
|
-
'claude': 'claude-desktop',
|
|
266
|
-
'claude-desktop': 'claude-desktop',
|
|
267
|
-
'claudedesktop': 'claude-desktop',
|
|
268
|
-
'desktop': 'claude-desktop',
|
|
269
|
-
'code': 'claude-code',
|
|
270
|
-
'claude-code': 'claude-code',
|
|
271
|
-
'claudecode': 'claude-code',
|
|
272
|
-
'codex': 'codex',
|
|
273
|
-
'openai': 'codex',
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const targetName = aliases[normalizedClient];
|
|
277
|
-
if (!targetName) {
|
|
278
|
-
console.log(chalk.red(`ā Unknown client: ${options.client}`));
|
|
279
|
-
console.log(' Supported: claude-desktop, claude-code, codex');
|
|
280
|
-
process.exit(1);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
targetClients = clients.filter(c => c.name === targetName);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Detect which clients exist
|
|
287
|
-
console.log('š Detecting MCP clients...\n');
|
|
288
|
-
|
|
289
|
-
const detectedClients: ClientConfig[] = [];
|
|
290
|
-
for (const client of targetClients) {
|
|
291
|
-
let exists = false;
|
|
292
|
-
|
|
293
|
-
// Special detection for Codex (check CLI availability)
|
|
294
|
-
if (client.name === 'codex') {
|
|
295
|
-
exists = isCodexAvailable();
|
|
296
|
-
} else {
|
|
297
|
-
// For JSON-based configs, check file/dir existence
|
|
298
|
-
const configPath = client.getConfigPath();
|
|
299
|
-
const configDir = dirname(configPath);
|
|
300
|
-
exists = existsSync(configPath) || existsSync(configDir);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const icon = exists ? chalk.green('ā') : chalk.gray('ā');
|
|
304
|
-
const status = exists ? 'found' : 'not found';
|
|
305
|
-
console.log(` ${icon} ${client.displayName} ${status}`);
|
|
306
|
-
|
|
307
|
-
if (exists) {
|
|
308
|
-
detectedClients.push(client);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
console.log('');
|
|
313
|
-
|
|
314
|
-
if (detectedClients.length === 0) {
|
|
315
|
-
console.log(chalk.yellow('ā ļø No MCP clients detected.'));
|
|
316
|
-
console.log(' Install Claude Desktop or Claude Code first.\n');
|
|
317
|
-
process.exit(0);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Install to each detected client
|
|
321
|
-
let successCount = 0;
|
|
322
|
-
let skipCount = 0;
|
|
323
|
-
let failCount = 0;
|
|
324
|
-
|
|
325
|
-
for (const client of detectedClients) {
|
|
326
|
-
const result = installToClient(client, options.dryRun || false);
|
|
327
|
-
|
|
328
|
-
if (result.success) {
|
|
329
|
-
if (result.skipped) {
|
|
330
|
-
skipCount++;
|
|
331
|
-
console.log(chalk.yellow(`āļø ${client.displayName}: ${result.message}`));
|
|
332
|
-
} else {
|
|
333
|
-
successCount++;
|
|
334
|
-
console.log(chalk.green(`ā ${client.displayName}: ${result.message}`));
|
|
335
|
-
}
|
|
336
|
-
} else {
|
|
337
|
-
failCount++;
|
|
338
|
-
console.log(chalk.red(`ā ${client.displayName}: ${result.message}`));
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Summary
|
|
343
|
-
console.log('\n' + 'ā'.repeat(50));
|
|
344
|
-
|
|
345
|
-
if (failCount === 0) {
|
|
346
|
-
if (options.dryRun) {
|
|
347
|
-
console.log(chalk.cyan('\nā
Dry run complete! Run without --dry-run to apply changes.\n'));
|
|
348
|
-
} else if (successCount > 0) {
|
|
349
|
-
console.log(chalk.green('\nā
APIClaw installed successfully!\n'));
|
|
350
|
-
console.log(chalk.bold('What you get:\n'));
|
|
351
|
-
console.log(chalk.cyan(' š Search') + ' 22,000+ APIs to discover');
|
|
352
|
-
console.log(chalk.cyan(' š Open APIs') + ' 1,600 free APIs');
|
|
353
|
-
console.log(chalk.cyan(' š Direct Call') + ' 1,500+ premium (APIClaw manages keys)');
|
|
354
|
-
console.log('');
|
|
355
|
-
console.log('Next:');
|
|
356
|
-
console.log(' 1. Restart your MCP client');
|
|
357
|
-
console.log(' 2. Try: "Find weather APIs"');
|
|
358
|
-
console.log('');
|
|
359
|
-
console.log('Docs: https://apiclaw.com/docs\n');
|
|
360
|
-
} else {
|
|
361
|
-
console.log(chalk.yellow('\nā
APIClaw already installed in all clients.\n'));
|
|
362
|
-
console.log(chalk.bold('What you have:\n'));
|
|
363
|
-
console.log(chalk.cyan(' š Search') + ' 22,000+ APIs to discover');
|
|
364
|
-
console.log(chalk.cyan(' š Open APIs') + ' 1,600 free APIs');
|
|
365
|
-
console.log(chalk.cyan(' š Direct Call') + ' 1,500+ premium (APIClaw manages keys)');
|
|
366
|
-
console.log('');
|
|
367
|
-
console.log('Run with --force to reinstall (coming soon).\n');
|
|
368
|
-
}
|
|
369
|
-
} else {
|
|
370
|
-
console.log(chalk.red(`\nā ļø Installation completed with ${failCount} error(s).\n`));
|
|
371
|
-
process.exit(1);
|
|
372
|
-
}
|
|
373
|
-
}
|