@nordsym/apiclaw 1.5.9 → 1.5.11

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 (109) hide show
  1. package/APILAYER_STATUS_2026-03-24.md +38 -0
  2. package/CHANGELOG-WHITELIST-V2.md +269 -0
  3. package/HIVR-INTEGRATION.md +281 -0
  4. package/HIVR-WHITELIST-STATUS.md +205 -0
  5. package/HIVR-WHITELIST.md +148 -0
  6. package/TERMINOLOGY-AUDIT.md +99 -0
  7. package/TERMINOLOGY-FIXED.md +74 -0
  8. package/VIDEO-DEMO-GUIDE.md +82 -0
  9. package/WHITELIST-ARCHITECTURE.md +379 -0
  10. package/api/discover.ts +71 -0
  11. package/api/health.ts +20 -0
  12. package/convex/http.d.ts.map +1 -1
  13. package/convex/http.js +8 -0
  14. package/convex/http.js.map +1 -1
  15. package/convex/http.ts +8 -0
  16. package/direct-test.mjs +51 -0
  17. package/dist/access-control.d.ts +45 -0
  18. package/dist/access-control.d.ts.map +1 -0
  19. package/dist/access-control.js +142 -0
  20. package/dist/access-control.js.map +1 -0
  21. package/dist/analytics.d.ts +4 -0
  22. package/dist/analytics.d.ts.map +1 -1
  23. package/dist/analytics.js +1 -0
  24. package/dist/analytics.js.map +1 -1
  25. package/dist/cli/commands/mcp-install.d.ts.map +1 -1
  26. package/dist/cli/commands/mcp-install.js +55 -40
  27. package/dist/cli/commands/mcp-install.js.map +1 -1
  28. package/dist/credentials.d.ts.map +1 -1
  29. package/dist/credentials.js +148 -0
  30. package/dist/credentials.js.map +1 -1
  31. package/dist/discovery.d.ts.map +1 -1
  32. package/dist/discovery.js +191 -82
  33. package/dist/discovery.js.map +1 -1
  34. package/dist/execute.d.ts.map +1 -1
  35. package/dist/execute.js +274 -0
  36. package/dist/execute.js.map +1 -1
  37. package/dist/hivr-whitelist.d.ts +18 -0
  38. package/dist/hivr-whitelist.d.ts.map +1 -0
  39. package/dist/hivr-whitelist.js +95 -0
  40. package/dist/hivr-whitelist.js.map +1 -0
  41. package/dist/http-api.d.ts.map +1 -1
  42. package/dist/http-api.js +17 -33
  43. package/dist/http-api.js.map +1 -1
  44. package/dist/http-server-minimal.d.ts +7 -0
  45. package/dist/http-server-minimal.d.ts.map +1 -0
  46. package/dist/http-server-minimal.js +126 -0
  47. package/dist/http-server-minimal.js.map +1 -0
  48. package/dist/product-whitelist.d.ts +37 -0
  49. package/dist/product-whitelist.d.ts.map +1 -0
  50. package/dist/product-whitelist.js +203 -0
  51. package/dist/product-whitelist.js.map +1 -0
  52. package/dist/proxy.d.ts.map +1 -1
  53. package/dist/proxy.js +1 -1
  54. package/dist/proxy.js.map +1 -1
  55. package/email-templates/README.md +104 -0
  56. package/email-templates/partnership-template.html +116 -0
  57. package/landing/next-env.d.ts +1 -0
  58. package/landing/pages/api/discover.ts +43 -0
  59. package/landing/pages/api/health.ts +20 -0
  60. package/landing/src/app/api/auth/magic-link/route.ts +1 -1
  61. package/landing/src/app/auth/verify/page.tsx +6 -0
  62. package/landing/src/app/dashboard/verify/page.tsx +6 -0
  63. package/landing/src/app/join/page.tsx +6 -0
  64. package/landing/src/app/layout.tsx +2 -2
  65. package/landing/src/app/login/page.tsx +1 -1
  66. package/landing/src/app/mou/[partnerId]/page.tsx +6 -0
  67. package/landing/src/app/page.tsx +39 -18
  68. package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +6 -0
  69. package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +5 -0
  70. package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +5 -0
  71. package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +6 -1
  72. package/landing/src/app/providers/dashboard/[apiId]/page.tsx +5 -0
  73. package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +5 -0
  74. package/landing/src/app/providers/dashboard/layout.tsx +6 -6
  75. package/landing/src/app/providers/dashboard/login/page.tsx +1 -1
  76. package/landing/src/app/providers/dashboard/page.tsx +1 -1
  77. package/landing/src/app/providers/dashboard/verify/page.tsx +6 -0
  78. package/landing/src/app/providers/layout.tsx +1 -1
  79. package/landing/src/app/upgrade/page.tsx +6 -0
  80. package/landing/src/app/workspace/page.tsx +6 -0
  81. package/landing/src/components/HeroTabs.tsx +2 -2
  82. package/landing/src/components/VideoDemo.tsx +94 -0
  83. package/landing/src/components/{ProviderDashboard.tsx → Workspace.tsx} +2 -2
  84. package/landing/src/lib/mock-data.ts +1 -1
  85. package/landing/src/lib/stats.json +1 -1
  86. package/package.json +4 -2
  87. package/scripts/test-whitelist-v2.sh +128 -0
  88. package/src/access-control.ts +174 -0
  89. package/src/analytics.ts +5 -0
  90. package/src/cli/commands/mcp-install.ts +14 -4
  91. package/src/credentials.ts +156 -0
  92. package/src/discovery.ts +191 -82
  93. package/src/execute.ts +274 -0
  94. package/src/hivr-whitelist.ts +110 -0
  95. package/src/http-api.ts +18 -34
  96. package/src/http-server-minimal.ts +154 -0
  97. package/src/product-whitelist.ts +246 -0
  98. package/src/proxy.ts +1 -1
  99. package/test-actual-handlers.ts +92 -0
  100. package/test-apilayer-all-14.ts +249 -0
  101. package/test-apilayer-fixed.ts +248 -0
  102. package/test-direct-endpoints.ts +174 -0
  103. package/test-exact-endpoints.ts +144 -0
  104. package/test-final.ts +83 -0
  105. package/test-full-routing.ts +100 -0
  106. package/test-handlers-correct.ts +217 -0
  107. package/test-numverify-key.ts +41 -0
  108. package/test-via-handlers.ts +92 -0
  109. package/test-worldnews.mjs +26 -0
@@ -167,6 +167,118 @@ const providers: Record<string, ProviderCredential> = {
167
167
  return null;
168
168
  },
169
169
  },
170
+
171
+ apilayer: {
172
+ type: 'api_key',
173
+ get(): APICredentials | null {
174
+ const env = loadEnvFile('apilayer.env');
175
+ // Return all keys — handler picks the right one per action
176
+ const keys: Record<string, string> = {};
177
+ for (const [k, v] of Object.entries(env)) {
178
+ if (k.startsWith('APILAYER_') && v) {
179
+ keys[k] = v;
180
+ }
181
+ }
182
+ if (Object.keys(keys).length === 0) return null;
183
+ return { type: 'api_key', api_key: keys.APILAYER_EXCHANGERATE_KEY || '', ...keys } as any;
184
+ },
185
+ },
186
+
187
+ groq: {
188
+ type: 'bearer',
189
+ get(): APICredentials | null {
190
+ const env = loadEnvFile('groq.env');
191
+ const key = env.GROQ_API_KEY || process.env.GROQ_API_KEY;
192
+ if (key) {
193
+ return { type: 'bearer', api_key: key };
194
+ }
195
+ return null;
196
+ },
197
+ },
198
+
199
+ deepgram: {
200
+ type: 'bearer',
201
+ get(): APICredentials | null {
202
+ const env = loadEnvFile('deepgram.env');
203
+ const key = env.DEEPGRAM_API_KEY || process.env.DEEPGRAM_API_KEY;
204
+ if (key) {
205
+ return { type: 'bearer', api_key: key };
206
+ }
207
+ return null;
208
+ },
209
+ },
210
+
211
+ mistral: {
212
+ type: 'api_key',
213
+ get(): APICredentials | null {
214
+ const env = loadEnvFile('mistral.env');
215
+ const key = env.MISTRAL_API_KEY || process.env.MISTRAL_API_KEY;
216
+ if (key) {
217
+ return { type: 'api_key', api_key: key };
218
+ }
219
+ return null;
220
+ },
221
+ },
222
+
223
+ cohere: {
224
+ type: 'api_key',
225
+ get(): APICredentials | null {
226
+ const env = loadEnvFile('cohere.env');
227
+ const key = env.COHERE_API_KEY || process.env.COHERE_API_KEY;
228
+ if (key) {
229
+ return { type: 'api_key', api_key: key };
230
+ }
231
+ return null;
232
+ },
233
+ },
234
+
235
+ serper: {
236
+ type: 'api_key',
237
+ get(): APICredentials | null {
238
+ const env = loadEnvFile('serpapi.env');
239
+ const key = env.SERPAPI_API_KEY || process.env.SERPER_API_KEY || process.env.SERPAPI_API_KEY;
240
+ if (key) {
241
+ return { type: 'api_key', api_key: key };
242
+ }
243
+ return null;
244
+ },
245
+ },
246
+
247
+ stability: {
248
+ type: 'api_key',
249
+ get(): APICredentials | null {
250
+ const env = loadEnvFile('stability.env');
251
+ const key = env.STABILITY_API_KEY || process.env.STABILITY_API_KEY;
252
+ if (key) {
253
+ return { type: 'api_key', api_key: key };
254
+ }
255
+ return null;
256
+ },
257
+ },
258
+
259
+ together: {
260
+ type: 'bearer',
261
+ get(): APICredentials | null {
262
+ const env = loadEnvFile('together.env');
263
+ const key = env.TOGETHER_API_KEY || process.env.TOGETHER_API_KEY;
264
+ if (key) {
265
+ return { type: 'bearer', api_key: key };
266
+ }
267
+ return null;
268
+ },
269
+ },
270
+
271
+ assemblyai: {
272
+ type: 'api_key',
273
+ get(): APICredentials | null {
274
+ const env = loadEnvFile('assemblyai.env');
275
+ const key = env.ASSEMBLYAI_API_KEY || process.env.ASSEMBLYAI_API_KEY;
276
+ if (key) {
277
+ return { type: 'api_key', api_key: key };
278
+ }
279
+ return null;
280
+ },
281
+ },
170
282
  };
171
283
 
172
284
  /**
@@ -215,6 +327,50 @@ export function hasRealCredentials(providerId: string): boolean {
215
327
  const env = loadEnvFile('e2b.env');
216
328
  return !!(env.E2B_API_KEY || process.env.E2B_API_KEY);
217
329
  }
330
+ if (providerId === 'firecrawl') {
331
+ const env = loadEnvFile('firecrawl.env');
332
+ return !!(env.FIRECRAWL_API_KEY || process.env.FIRECRAWL_API_KEY);
333
+ }
334
+ if (providerId === 'github') {
335
+ const env = loadEnvFile('github.env');
336
+ return !!(env.GITHUB_TOKEN || process.env.GITHUB_TOKEN);
337
+ }
338
+ if (providerId === 'apilayer') {
339
+ const env = loadEnvFile('apilayer.env');
340
+ return !!(env.APILAYER_EXCHANGERATE_KEY || process.env.APILAYER_EXCHANGERATE_KEY);
341
+ }
342
+ if (providerId === 'groq') {
343
+ const env = loadEnvFile('groq.env');
344
+ return !!(env.GROQ_API_KEY || process.env.GROQ_API_KEY);
345
+ }
346
+ if (providerId === 'deepgram') {
347
+ const env = loadEnvFile('deepgram.env');
348
+ return !!(env.DEEPGRAM_API_KEY || process.env.DEEPGRAM_API_KEY);
349
+ }
350
+ if (providerId === 'mistral') {
351
+ const env = loadEnvFile('mistral.env');
352
+ return !!(env.MISTRAL_API_KEY || process.env.MISTRAL_API_KEY);
353
+ }
354
+ if (providerId === 'cohere') {
355
+ const env = loadEnvFile('cohere.env');
356
+ return !!(env.COHERE_API_KEY || process.env.COHERE_API_KEY);
357
+ }
358
+ if (providerId === 'serper') {
359
+ const env = loadEnvFile('serpapi.env');
360
+ return !!(env.SERPAPI_API_KEY || process.env.SERPER_API_KEY || process.env.SERPAPI_API_KEY);
361
+ }
362
+ if (providerId === 'stability') {
363
+ const env = loadEnvFile('stability.env');
364
+ return !!(env.STABILITY_API_KEY || process.env.STABILITY_API_KEY);
365
+ }
366
+ if (providerId === 'together') {
367
+ const env = loadEnvFile('together.env');
368
+ return !!(env.TOGETHER_API_KEY || process.env.TOGETHER_API_KEY);
369
+ }
370
+ if (providerId === 'assemblyai') {
371
+ const env = loadEnvFile('assemblyai.env');
372
+ return !!(env.ASSEMBLYAI_API_KEY || process.env.ASSEMBLYAI_API_KEY);
373
+ }
218
374
  return false;
219
375
  }
220
376
 
package/src/discovery.ts CHANGED
@@ -17,73 +17,13 @@ const apisData = JSON.parse(
17
17
  const apis: APIProvider[] = apisData.apis;
18
18
 
19
19
  // Direct Call provider specs (hardcoded handlers with params)
20
+ // Ordered: AI-first (models, LLM routing, audio), then infrastructure (code, web, search, email, SMS)
20
21
  const DIRECT_CALL_SPECS: Record<string, {
21
22
  description: string;
22
23
  auth: string;
23
24
  docs: string;
24
25
  actions: Record<string, { params: { name: string; required: boolean; desc: string }[]; desc: string }>;
25
26
  }> = {
26
- '46elks': {
27
- description: 'Swedish SMS and voice API',
28
- auth: 'basic',
29
- docs: 'https://46elks.com/docs',
30
- actions: {
31
- send_sms: {
32
- desc: 'Send SMS message',
33
- params: [
34
- { name: 'to', required: true, desc: 'Phone number (+46...)' },
35
- { name: 'message', required: true, desc: 'SMS text (max 160 chars for 1 segment)' },
36
- { name: 'from', required: false, desc: 'Sender ID (default: APIClaw)' },
37
- ],
38
- },
39
- },
40
- },
41
- twilio: {
42
- description: 'Global SMS and voice API',
43
- auth: 'basic',
44
- docs: 'https://www.twilio.com/docs',
45
- actions: {
46
- send_sms: {
47
- desc: 'Send SMS message',
48
- params: [
49
- { name: 'to', required: true, desc: 'Phone number (E.164 format)' },
50
- { name: 'message', required: true, desc: 'SMS text' },
51
- { name: 'from', required: false, desc: 'Sender phone number' },
52
- ],
53
- },
54
- },
55
- },
56
- brave_search: {
57
- description: 'Web search API',
58
- auth: 'api_key',
59
- docs: 'https://api.search.brave.com/docs',
60
- actions: {
61
- search: {
62
- desc: 'Search the web',
63
- params: [
64
- { name: 'query', required: true, desc: 'Search query' },
65
- { name: 'count', required: false, desc: 'Number of results (default: 5)' },
66
- ],
67
- },
68
- },
69
- },
70
- resend: {
71
- description: 'Email API',
72
- auth: 'bearer',
73
- docs: 'https://resend.com/docs',
74
- actions: {
75
- send_email: {
76
- desc: 'Send email',
77
- params: [
78
- { name: 'to', required: true, desc: 'Recipient email' },
79
- { name: 'subject', required: true, desc: 'Email subject' },
80
- { name: 'html', required: false, desc: 'HTML body' },
81
- { name: 'text', required: false, desc: 'Plain text body' },
82
- { name: 'from', required: false, desc: 'Sender (default: noreply@apiclaw.nordsym.com)' },
83
- ],
84
- },
85
- },
86
- },
87
27
  openrouter: {
88
28
  description: 'LLM routing (100+ models)',
89
29
  auth: 'bearer',
@@ -99,6 +39,24 @@ const DIRECT_CALL_SPECS: Record<string, {
99
39
  },
100
40
  },
101
41
  },
42
+ replicate: {
43
+ description: 'Run any AI model (images, video, audio)',
44
+ auth: 'bearer',
45
+ docs: 'https://replicate.com/docs',
46
+ actions: {
47
+ run: {
48
+ desc: 'Run a model',
49
+ params: [
50
+ { name: 'model', required: true, desc: 'Model ID (e.g., stability-ai/sdxl:...)' },
51
+ { name: 'input', required: true, desc: 'Model input parameters' },
52
+ ],
53
+ },
54
+ list_models: {
55
+ desc: 'List available models',
56
+ params: [],
57
+ },
58
+ },
59
+ },
102
60
  elevenlabs: {
103
61
  description: 'Text-to-speech',
104
62
  auth: 'api_key',
@@ -114,21 +72,23 @@ const DIRECT_CALL_SPECS: Record<string, {
114
72
  },
115
73
  },
116
74
  },
117
- replicate: {
118
- description: 'Run any AI model (images, video, audio)',
119
- auth: 'bearer',
120
- docs: 'https://replicate.com/docs',
75
+ e2b: {
76
+ description: 'Code sandbox for AI agents',
77
+ auth: 'api_key',
78
+ docs: 'https://e2b.dev/docs',
121
79
  actions: {
122
- run: {
123
- desc: 'Run a model',
80
+ run_code: {
81
+ desc: 'Execute code in sandbox',
124
82
  params: [
125
- { name: 'model', required: true, desc: 'Model ID (e.g., stability-ai/sdxl:...)' },
126
- { name: 'input', required: true, desc: 'Model input parameters' },
83
+ { name: 'code', required: true, desc: 'Code to run' },
84
+ { name: 'language', required: false, desc: 'Language (default: python)' },
127
85
  ],
128
86
  },
129
- list_models: {
130
- desc: 'List available models',
131
- params: [],
87
+ run_shell: {
88
+ desc: 'Execute shell command',
89
+ params: [
90
+ { name: 'command', required: true, desc: 'Shell command' },
91
+ ],
132
92
  },
133
93
  },
134
94
  },
@@ -206,22 +166,171 @@ const DIRECT_CALL_SPECS: Record<string, {
206
166
  },
207
167
  },
208
168
  },
209
- e2b: {
210
- description: 'Code sandbox for AI agents',
169
+ brave_search: {
170
+ description: 'Web search API',
211
171
  auth: 'api_key',
212
- docs: 'https://e2b.dev/docs',
172
+ docs: 'https://api.search.brave.com/docs',
213
173
  actions: {
214
- run_code: {
215
- desc: 'Execute code in sandbox',
174
+ search: {
175
+ desc: 'Search the web',
216
176
  params: [
217
- { name: 'code', required: true, desc: 'Code to run' },
218
- { name: 'language', required: false, desc: 'Language (default: python)' },
177
+ { name: 'query', required: true, desc: 'Search query' },
178
+ { name: 'count', required: false, desc: 'Number of results (default: 5)' },
219
179
  ],
220
180
  },
221
- run_shell: {
222
- desc: 'Execute shell command',
181
+ },
182
+ },
183
+ resend: {
184
+ description: 'Email API',
185
+ auth: 'bearer',
186
+ docs: 'https://resend.com/docs',
187
+ actions: {
188
+ send_email: {
189
+ desc: 'Send email',
223
190
  params: [
224
- { name: 'command', required: true, desc: 'Shell command' },
191
+ { name: 'to', required: true, desc: 'Recipient email' },
192
+ { name: 'subject', required: true, desc: 'Email subject' },
193
+ { name: 'html', required: false, desc: 'HTML body' },
194
+ { name: 'text', required: false, desc: 'Plain text body' },
195
+ { name: 'from', required: false, desc: 'Sender (default: noreply@apiclaw.nordsym.com)' },
196
+ ],
197
+ },
198
+ },
199
+ },
200
+ '46elks': {
201
+ description: 'Swedish SMS and voice API',
202
+ auth: 'basic',
203
+ docs: 'https://46elks.com/docs',
204
+ actions: {
205
+ send_sms: {
206
+ desc: 'Send SMS message',
207
+ params: [
208
+ { name: 'to', required: true, desc: 'Phone number (+46...)' },
209
+ { name: 'message', required: true, desc: 'SMS text (max 160 chars for 1 segment)' },
210
+ { name: 'from', required: false, desc: 'Sender ID (default: APIClaw)' },
211
+ ],
212
+ },
213
+ },
214
+ },
215
+ twilio: {
216
+ description: 'Global SMS and voice API',
217
+ auth: 'basic',
218
+ docs: 'https://www.twilio.com/docs',
219
+ actions: {
220
+ send_sms: {
221
+ desc: 'Send SMS message',
222
+ params: [
223
+ { name: 'to', required: true, desc: 'Phone number (E.164 format)' },
224
+ { name: 'message', required: true, desc: 'SMS text' },
225
+ { name: 'from', required: false, desc: 'Sender phone number' },
226
+ ],
227
+ },
228
+ },
229
+ },
230
+ apilayer: {
231
+ description: 'APILayer marketplace — currency, news, scraping, PDFs, verification & more',
232
+ auth: 'api_key',
233
+ docs: 'https://apilayer.com',
234
+ actions: {
235
+ exchange_rates: {
236
+ desc: 'Get live or historical currency exchange rates',
237
+ params: [
238
+ { name: 'base', required: false, desc: 'Base currency (default: USD)' },
239
+ { name: 'symbols', required: false, desc: 'Comma-separated target currencies' },
240
+ { name: 'date', required: false, desc: 'Historical date YYYY-MM-DD (omit for live)' },
241
+ ],
242
+ },
243
+ market_data: {
244
+ desc: 'End-of-day stock market data',
245
+ params: [
246
+ { name: 'symbols', required: true, desc: 'Stock ticker(s), comma-separated e.g. AAPL,MSFT' },
247
+ { name: 'date_from', required: false, desc: 'Start date YYYY-MM-DD' },
248
+ { name: 'date_to', required: false, desc: 'End date YYYY-MM-DD' },
249
+ ],
250
+ },
251
+ aviation: {
252
+ desc: 'Real-time flight data and tracking',
253
+ params: [
254
+ { name: 'flight_iata', required: false, desc: 'IATA flight number e.g. AA100' },
255
+ { name: 'dep_iata', required: false, desc: 'Departure airport IATA code' },
256
+ { name: 'arr_iata', required: false, desc: 'Arrival airport IATA code' },
257
+ ],
258
+ },
259
+ pdf_generate: {
260
+ desc: 'Generate PDF from URL or HTML',
261
+ params: [
262
+ { name: 'document_url', required: false, desc: 'URL to convert to PDF' },
263
+ { name: 'document_html', required: false, desc: 'HTML string to convert (alternative to URL)' },
264
+ { name: 'page_size', required: false, desc: 'Page size: A4, Letter, etc (default: A4)' },
265
+ ],
266
+ },
267
+ screenshot: {
268
+ desc: 'Capture full-page screenshot of any URL',
269
+ params: [
270
+ { name: 'url', required: true, desc: 'URL to screenshot' },
271
+ { name: 'viewport', required: false, desc: 'Viewport size e.g. 1440x900 (default)' },
272
+ { name: 'fullpage', required: false, desc: '1 for full page, 0 for viewport only (default: 0)' },
273
+ ],
274
+ },
275
+ verify_email: {
276
+ desc: 'Validate email address format and deliverability',
277
+ params: [
278
+ { name: 'email', required: true, desc: 'Email address to verify' },
279
+ ],
280
+ },
281
+ verify_number: {
282
+ desc: 'Validate and lookup phone number details',
283
+ params: [
284
+ { name: 'number', required: true, desc: 'Phone number in E.164 format e.g. +46701234567' },
285
+ ],
286
+ },
287
+ vat_check: {
288
+ desc: 'Validate EU VAT number',
289
+ params: [
290
+ { name: 'vat_number', required: true, desc: 'EU VAT number e.g. SE556012345601' },
291
+ ],
292
+ },
293
+ world_news: {
294
+ desc: 'Extract and analyze news articles from a URL',
295
+ params: [
296
+ { name: 'url', required: true, desc: 'URL of the news article to analyze' },
297
+ { name: 'analyze', required: false, desc: 'Whether to analyze the news (default: true)' },
298
+ ],
299
+ },
300
+ finance_news: {
301
+ desc: 'Latest financial and stock market news',
302
+ params: [
303
+ { name: 'tickers', required: false, desc: 'Stock tickers comma-separated e.g. AAPL,TSLA' },
304
+ { name: 'text', required: false, desc: 'Keyword filter' },
305
+ { name: 'number', required: false, desc: 'Number of results (default: 5)' },
306
+ ],
307
+ },
308
+ scrape: {
309
+ desc: 'Advanced web scraper — returns clean page content',
310
+ params: [
311
+ { name: 'url', required: true, desc: 'URL to scrape' },
312
+ ],
313
+ },
314
+ image_crop: {
315
+ desc: 'Smart crop an image to specified dimensions',
316
+ params: [
317
+ { name: 'url', required: true, desc: 'Image URL to crop' },
318
+ { name: 'width', required: false, desc: 'Target width in pixels' },
319
+ { name: 'height', required: false, desc: 'Target height in pixels' },
320
+ ],
321
+ },
322
+ skills: {
323
+ desc: 'Search 7000+ professional skills database',
324
+ params: [
325
+ { name: 'q', required: true, desc: 'Skill search query e.g. "machine learning"' },
326
+ { name: 'count', required: false, desc: 'Number of results (default: 10)' },
327
+ ],
328
+ },
329
+ form_submit: {
330
+ desc: 'Submit form data to a FormAPI endpoint',
331
+ params: [
332
+ { name: 'endpoint', required: true, desc: 'FormAPI endpoint path' },
333
+ { name: 'data', required: false, desc: 'Form data object to submit' },
225
334
  ],
226
335
  },
227
336
  },