@nordsym/apiclaw 2.2.0 → 2.3.0

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.
Files changed (176) hide show
  1. package/README.md +15 -2
  2. package/dist/bin-http.js +0 -0
  3. package/dist/bin.bundled.js +79288 -0
  4. package/dist/gateway-client.d.ts.map +1 -1
  5. package/dist/gateway-client.js +24 -2
  6. package/dist/gateway-client.js.map +1 -1
  7. package/dist/index.bundled.js +61263 -0
  8. package/dist/index.js +2 -2
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -2
  11. package/.claude/settings.local.json +0 -13
  12. package/.env.prod +0 -1
  13. package/apiclaw-README.md +0 -494
  14. package/convex/_generated/api.d.ts +0 -145
  15. package/convex/_generated/api.js +0 -23
  16. package/convex/_generated/dataModel.d.ts +0 -60
  17. package/convex/_generated/server.d.ts +0 -143
  18. package/convex/_generated/server.js +0 -93
  19. package/convex/_listWorkspaces.ts +0 -13
  20. package/convex/adminActivate.ts +0 -53
  21. package/convex/adminStats.ts +0 -306
  22. package/convex/agents.ts +0 -939
  23. package/convex/analytics.ts +0 -187
  24. package/convex/apiKeys.ts +0 -220
  25. package/convex/backfillAnalytics.ts +0 -272
  26. package/convex/backfillSearchLogs.ts +0 -35
  27. package/convex/billing.ts +0 -834
  28. package/convex/capabilities.ts +0 -157
  29. package/convex/chains.ts +0 -1318
  30. package/convex/credits.ts +0 -211
  31. package/convex/crons.ts +0 -65
  32. package/convex/debugFilestackLogs.ts +0 -16
  33. package/convex/debugGetToken.ts +0 -18
  34. package/convex/directCall.ts +0 -713
  35. package/convex/earnProgress.ts +0 -753
  36. package/convex/email.ts +0 -329
  37. package/convex/feedback.ts +0 -265
  38. package/convex/funnel.ts +0 -431
  39. package/convex/guards.ts +0 -174
  40. package/convex/http.ts +0 -3756
  41. package/convex/inbound.ts +0 -32
  42. package/convex/logs.ts +0 -701
  43. package/convex/migrateFilestack.ts +0 -81
  44. package/convex/migratePartnersProd.ts +0 -174
  45. package/convex/migratePratham.ts +0 -126
  46. package/convex/migrateProviderWorkspaces.ts +0 -175
  47. package/convex/mou.ts +0 -91
  48. package/convex/nurture.ts +0 -355
  49. package/convex/providerKeys.ts +0 -289
  50. package/convex/providers.ts +0 -1135
  51. package/convex/purchases.ts +0 -183
  52. package/convex/ratelimit.ts +0 -104
  53. package/convex/schema.ts +0 -926
  54. package/convex/searchLogs.ts +0 -265
  55. package/convex/seedAPILayerAPIs.ts +0 -191
  56. package/convex/seedDirectCallConfigs.ts +0 -336
  57. package/convex/seedPratham.ts +0 -149
  58. package/convex/spendAlerts.ts +0 -442
  59. package/convex/stripeActions.ts +0 -607
  60. package/convex/teams.ts +0 -243
  61. package/convex/telemetry.ts +0 -81
  62. package/convex/tsconfig.json +0 -25
  63. package/convex/updateAPIStatus.ts +0 -44
  64. package/convex/usage.ts +0 -260
  65. package/convex/usageReports.ts +0 -357
  66. package/convex/waitlist.ts +0 -55
  67. package/convex/webhooks.ts +0 -494
  68. package/convex/workspaceSettings.ts +0 -143
  69. package/convex/workspaces.ts +0 -1331
  70. package/convex.json +0 -3
  71. package/direct-test.mjs +0 -51
  72. package/email-templates/filestack-provider-outreach.html +0 -162
  73. package/email-templates/partnership-template.html +0 -116
  74. package/email-templates/pratham-draft-preview.txt +0 -57
  75. package/email-templates/pratham-partnership-draft.html +0 -141
  76. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  77. package/reports/pipeline/PIPELINE-REPORT.json +0 -153
  78. package/reports/pipeline/acquire_apisguru.json +0 -17
  79. package/reports/pipeline/capabilities.json +0 -38
  80. package/reports/pipeline/discover_azure_recursive.json +0 -1551
  81. package/reports/pipeline/discover_github.json +0 -25
  82. package/reports/pipeline/discover_github_repos.json +0 -49
  83. package/reports/pipeline/discover_swaggerhub.json +0 -24
  84. package/reports/pipeline/discover_well_known.json +0 -23
  85. package/reports/pipeline/fetch_specs.json +0 -19
  86. package/reports/pipeline/generate_providers.json +0 -14
  87. package/reports/pipeline/match_registry.json +0 -11
  88. package/reports/pipeline/parse_specs.json +0 -17
  89. package/reports/pipeline/promote_candidates.json +0 -34
  90. package/reports/pipeline/validate.json +0 -30
  91. package/reports/pipeline/validate_smoke_details.json +0 -3835
  92. package/reports/session-report-2026-04-05.html +0 -433
  93. package/seed-apis-direct.mjs +0 -106
  94. package/src/access-control.ts +0 -174
  95. package/src/adapters/base.ts +0 -364
  96. package/src/adapters/claude-desktop.ts +0 -41
  97. package/src/adapters/cline.ts +0 -88
  98. package/src/adapters/continue.ts +0 -91
  99. package/src/adapters/cursor.ts +0 -43
  100. package/src/adapters/custom.ts +0 -188
  101. package/src/adapters/detect.ts +0 -202
  102. package/src/adapters/index.ts +0 -47
  103. package/src/adapters/windsurf.ts +0 -44
  104. package/src/bin-http.ts +0 -45
  105. package/src/bin.ts +0 -34
  106. package/src/capability-router.ts +0 -331
  107. package/src/chainExecutor.ts +0 -730
  108. package/src/chainResolver.test.ts +0 -246
  109. package/src/chainResolver.ts +0 -658
  110. package/src/cli/commands/demo.ts +0 -109
  111. package/src/cli/commands/doctor.ts +0 -435
  112. package/src/cli/commands/index.ts +0 -9
  113. package/src/cli/commands/login.ts +0 -203
  114. package/src/cli/commands/mcp-install.ts +0 -373
  115. package/src/cli/commands/restore.ts +0 -333
  116. package/src/cli/commands/setup.ts +0 -297
  117. package/src/cli/commands/uninstall.ts +0 -240
  118. package/src/cli/index.ts +0 -148
  119. package/src/cli.ts +0 -370
  120. package/src/confirmation.ts +0 -296
  121. package/src/credentials.ts +0 -455
  122. package/src/credits.ts +0 -329
  123. package/src/crypto.ts +0 -75
  124. package/src/discovery.ts +0 -568
  125. package/src/enterprise/env.ts +0 -156
  126. package/src/enterprise/index.ts +0 -7
  127. package/src/enterprise/script-generator.ts +0 -481
  128. package/src/execute-dynamic.ts +0 -617
  129. package/src/execute.ts +0 -2386
  130. package/src/funnel-client.ts +0 -168
  131. package/src/funnel.test.ts +0 -187
  132. package/src/gateway-client.ts +0 -192
  133. package/src/hivr-whitelist.ts +0 -110
  134. package/src/http-api.ts +0 -286
  135. package/src/http-server-minimal.ts +0 -154
  136. package/src/index.ts +0 -2702
  137. package/src/intelligent-gateway.ts +0 -339
  138. package/src/mcp-analytics.ts +0 -156
  139. package/src/metered.ts +0 -149
  140. package/src/open-apis-generated.ts +0 -157
  141. package/src/open-apis.ts +0 -558
  142. package/src/postinstall.ts +0 -40
  143. package/src/product-whitelist.ts +0 -246
  144. package/src/proxy.ts +0 -36
  145. package/src/registration-guard.ts +0 -117
  146. package/src/session.ts +0 -129
  147. package/src/stripe.ts +0 -497
  148. package/src/telemetry.ts +0 -71
  149. package/src/test.ts +0 -135
  150. package/src/types/convex-api.d.ts +0 -20
  151. package/src/types/convex-api.ts +0 -21
  152. package/src/types.ts +0 -109
  153. package/src/ui/colors.ts +0 -219
  154. package/src/ui/errors.ts +0 -394
  155. package/src/ui/index.ts +0 -17
  156. package/src/ui/prompts.ts +0 -390
  157. package/src/ui/spinner.ts +0 -325
  158. package/src/utils/backup.ts +0 -224
  159. package/src/utils/config.ts +0 -318
  160. package/src/utils/os.ts +0 -124
  161. package/src/utils/paths.ts +0 -203
  162. package/src/webhook.ts +0 -107
  163. package/test-10-working.cjs +0 -97
  164. package/test-14-final.cjs +0 -96
  165. package/test-actual-handlers.ts +0 -92
  166. package/test-apilayer-all-14.ts +0 -249
  167. package/test-apilayer-fixed.ts +0 -248
  168. package/test-direct-endpoints.ts +0 -174
  169. package/test-exact-endpoints.ts +0 -144
  170. package/test-final.ts +0 -83
  171. package/test-full-routing.ts +0 -100
  172. package/test-handlers-correct.ts +0 -217
  173. package/test-numverify-key.ts +0 -41
  174. package/test-via-handlers.ts +0 -92
  175. package/test-worldnews.mjs +0 -26
  176. 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
- }