@muxoai/sdk 1.3.0 → 1.4.3
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/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +19 -4
- package/.codex/config.toml +9 -2
- package/ARCHITECTURE.md +16 -6
- package/README.md +69 -24
- package/dist/clerk.d.ts +35 -0
- package/dist/clerk.js +201 -0
- package/dist/cli.js +1677 -103
- package/dist/composio-catalog.d.ts +234 -0
- package/dist/composio-catalog.js +1894 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +27 -1
- package/dist/durable-workflow.d.ts +112 -0
- package/dist/durable-workflow.js +284 -0
- package/dist/engine.d.ts +377 -6
- package/dist/engine.js +1919 -94
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp-schema.d.ts +286 -0
- package/dist/mcp-schema.js +2580 -0
- package/dist/mcp.d.ts +2 -25
- package/dist/mcp.js +1008 -507
- package/dist/memory-graph.d.ts +45 -0
- package/dist/memory-graph.js +293 -0
- package/dist/nl.js +214 -35
- package/dist/oauth.d.ts +32 -0
- package/dist/oauth.js +134 -0
- package/dist/oss-suite.d.ts +217 -0
- package/dist/oss-suite.js +836 -0
- package/dist/policy.d.ts +190 -0
- package/dist/policy.js +1597 -0
- package/dist/providers/composio-instant.d.ts +4 -0
- package/dist/providers/composio-instant.js +890 -535
- package/dist/providers/composio-sdk.d.ts +126 -5
- package/dist/providers/composio-sdk.js +902 -79
- package/dist/providers/microsoft-governance.d.ts +89 -3
- package/dist/providers/microsoft-governance.js +673 -26
- package/dist/providers/stripe-billing.d.ts +42 -0
- package/dist/providers/stripe-billing.js +244 -5
- package/dist/providers/stripe-projects.d.ts +459 -4
- package/dist/providers/stripe-projects.js +2674 -122
- package/dist/providers/supermemory.d.ts +102 -6
- package/dist/providers/supermemory.js +609 -55
- package/dist/provisioning-managed.d.ts +50 -0
- package/dist/provisioning-managed.js +155 -0
- package/dist/sdk.d.ts +728 -7
- package/dist/sdk.js +539 -10
- package/dist/server.js +2744 -321
- package/dist/store.d.ts +138 -6
- package/dist/store.js +932 -58
- package/dist/types.d.ts +330 -4
- package/dist/ui/console-html.d.ts +62 -0
- package/dist/ui/console-html.js +3742 -2228
- package/dist/ui/design-tokens.d.ts +177 -0
- package/dist/ui/design-tokens.js +210 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +6 -5
- package/plugins/muxo/.app.json +1 -1
- package/plugins/muxo/.claude-plugin/plugin.json +3 -3
- package/plugins/muxo/.codex-plugin/plugin.json +19 -10
- package/plugins/muxo/.cursor-plugin/plugin.json +30 -0
- package/plugins/muxo/.mcp.json +1 -33
- package/plugins/muxo/assets/logo.png +0 -0
- package/plugins/muxo/hooks/session-start.sh +16 -4
- package/plugins/muxo/hooks/user-prompt-submit.sh +14 -1
- package/plugins/muxo/mcp.json +0 -10
- package/plugins/muxo/plugin.json +65 -8
- package/plugins/muxo/skills/muxo/SKILL.md +142 -14
- package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
- package/plugins/muxo/skills/muxo/references/cli.md +9 -3
- package/plugins/muxo/skills/muxo/references/mcp.md +22 -10
- package/plugins/muxo/skills/muxo-activity-summary/SKILL.md +81 -0
- package/plugins/muxo/skills/setup/SKILL.md +17 -3
- package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
- package/src/catalogues/stripe-projects-catalog.json +140 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import vm from "node:vm";
|
|
2
|
+
import { Composio } from "@composio/core";
|
|
1
3
|
const INSTANT_TOOLS = [
|
|
2
4
|
{
|
|
3
5
|
slug: "instant.web.search",
|
|
@@ -163,11 +165,11 @@ const INSTANT_TOOLS = [
|
|
|
163
165
|
{
|
|
164
166
|
slug: "instant.composio.workbench",
|
|
165
167
|
upstreamSlug: "COMPOSIO_REMOTE_WORKBENCH",
|
|
166
|
-
name: "
|
|
167
|
-
description: "Execute isolated data processing, JSON transformation, or Python/JS analysis in
|
|
168
|
+
name: "Instant: Remote Sandbox Workbench",
|
|
169
|
+
description: "Execute isolated data processing, JSON transformation, or Python/JS analysis in Muxo's remote sandbox workbench without OAuth.",
|
|
168
170
|
lane: "composio_instant",
|
|
169
171
|
providerSlug: "composio",
|
|
170
|
-
providerName: "
|
|
172
|
+
providerName: "Muxo Workbench",
|
|
171
173
|
categories: ["sandbox", "compute", "analysis", "instant"],
|
|
172
174
|
riskLevel: "write",
|
|
173
175
|
requiresAuth: false,
|
|
@@ -185,6 +187,52 @@ const INSTANT_TOOLS = [
|
|
|
185
187
|
type: "string",
|
|
186
188
|
description: "Brief explanation of what the workbench run is computing",
|
|
187
189
|
},
|
|
190
|
+
vault_bindings: {
|
|
191
|
+
type: "object",
|
|
192
|
+
description: "Optional masked vault secret bindings injected into the isolated sandbox environment",
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
slug: "instant.security.headers_audit",
|
|
199
|
+
upstreamSlug: "COMPOSIO_SECURITY_HEADERS_AUDIT",
|
|
200
|
+
name: "Instant HTTP Security & CSP Header Auditor",
|
|
201
|
+
description: "Audits any URL or domain for OWASP HTTP security headers (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy) and computes a security posture score.",
|
|
202
|
+
lane: "composio_instant",
|
|
203
|
+
providerSlug: "security_edge",
|
|
204
|
+
providerName: "Edge Security Auditor",
|
|
205
|
+
categories: ["security", "compliance", "audit", "instant"],
|
|
206
|
+
riskLevel: "read",
|
|
207
|
+
requiresAuth: false,
|
|
208
|
+
creditCost: 1,
|
|
209
|
+
billingMode: "provider_wallet",
|
|
210
|
+
inputSchema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
required: ["url"],
|
|
213
|
+
properties: {
|
|
214
|
+
url: { type: "string", description: "Target URL or domain to audit" },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
slug: "instant.ai.llms_txt_validator",
|
|
220
|
+
upstreamSlug: "COMPOSIO_AI_LLMS_TXT_VALIDATOR",
|
|
221
|
+
name: "Instant AI Readiness & llms.txt Validator",
|
|
222
|
+
description: "Inspects a domain's AI agent readiness (/llms.txt, /llms-full.txt, /.well-known/mcp.json, /skill.md, robots.txt AI crawler rules) and scores agent discoverability.",
|
|
223
|
+
lane: "composio_instant",
|
|
224
|
+
providerSlug: "ai_readiness",
|
|
225
|
+
providerName: "Edge AI Readiness Scanner",
|
|
226
|
+
categories: ["ai", "seo", "mcp", "instant"],
|
|
227
|
+
riskLevel: "read",
|
|
228
|
+
requiresAuth: false,
|
|
229
|
+
creditCost: 1,
|
|
230
|
+
billingMode: "provider_wallet",
|
|
231
|
+
inputSchema: {
|
|
232
|
+
type: "object",
|
|
233
|
+
required: ["domain"],
|
|
234
|
+
properties: {
|
|
235
|
+
domain: { type: "string", description: "Domain or URL to validate for AI agent discoverability" },
|
|
188
236
|
},
|
|
189
237
|
},
|
|
190
238
|
},
|
|
@@ -211,13 +259,159 @@ export class ComposioInstantAdapter {
|
|
|
211
259
|
}
|
|
212
260
|
async executeTool(params) {
|
|
213
261
|
const { tool, input } = params;
|
|
214
|
-
|
|
215
|
-
|
|
262
|
+
const isMasterWalletEmpty = Boolean(params.masterWalletDepleted) ||
|
|
263
|
+
process.env.MUXO_MASTER_WALLET_DEPLETED === "true" ||
|
|
264
|
+
input._simulateMasterWalletEmpty === true;
|
|
265
|
+
const hasCustomerByok = Boolean((params.composioApiKey && params.composioApiKey.trim()) ||
|
|
266
|
+
(params.exaApiKey && params.exaApiKey.trim()) ||
|
|
267
|
+
(params.firecrawlApiKey && params.firecrawlApiKey.trim()));
|
|
268
|
+
const activeComposioKey = params.composioApiKey ||
|
|
269
|
+
(isMasterWalletEmpty ? null : this.config.composioInstantApiKey || this.config.composioApiKey);
|
|
270
|
+
const finalizeInstantResult = (res) => {
|
|
271
|
+
if (isMasterWalletEmpty && !hasCustomerByok) {
|
|
272
|
+
return {
|
|
273
|
+
...res,
|
|
274
|
+
masterWalletFailover: "zero_cost_native_edge",
|
|
275
|
+
fundingSource: "zero_cost_native_edge_failover",
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
if (hasCustomerByok) {
|
|
279
|
+
return {
|
|
280
|
+
...res,
|
|
281
|
+
fundingSource: "customer_byok",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
return res;
|
|
285
|
+
};
|
|
286
|
+
// 0. Try Customer BYOK Exa / Firecrawl keys if configured
|
|
287
|
+
if (tool.slug === "instant.web.search" && params.exaApiKey) {
|
|
288
|
+
const query = String(input.query || "").trim();
|
|
289
|
+
const limit = Math.min(10, Math.max(1, Number(input.limit || 5)));
|
|
290
|
+
if (query) {
|
|
291
|
+
try {
|
|
292
|
+
const exaRes = await fetch("https://api.exa.ai/search", {
|
|
293
|
+
method: "POST",
|
|
294
|
+
headers: {
|
|
295
|
+
"x-api-key": params.exaApiKey,
|
|
296
|
+
"Content-Type": "application/json",
|
|
297
|
+
},
|
|
298
|
+
body: JSON.stringify({ query, numResults: limit, type: "auto" }),
|
|
299
|
+
signal: AbortSignal.timeout(8000),
|
|
300
|
+
});
|
|
301
|
+
if (exaRes.ok) {
|
|
302
|
+
const exaJson = (await exaRes.json());
|
|
303
|
+
if (Array.isArray(exaJson.results) && exaJson.results.length > 0) {
|
|
304
|
+
return finalizeInstantResult({
|
|
305
|
+
provider: "composio_instant",
|
|
306
|
+
billingMode: "customer_byok",
|
|
307
|
+
tool: tool.slug,
|
|
308
|
+
query,
|
|
309
|
+
results: exaJson.results.slice(0, limit).map((r) => ({
|
|
310
|
+
title: String(r.title || r.url || ""),
|
|
311
|
+
url: String(r.url || ""),
|
|
312
|
+
snippet: String(r.text || r.highlight || `Author: ${r.author || "web"}`),
|
|
313
|
+
source: "exa_byok",
|
|
314
|
+
})),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// Fall through to native edge search
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (tool.slug === "instant.web.scrape" && params.firecrawlApiKey) {
|
|
325
|
+
const targetUrl = String(input.url || "").trim();
|
|
326
|
+
const maxChars = Math.min(20000, Math.max(500, Number(input.maxChars || 4000)));
|
|
327
|
+
if (/^https?:\/\//i.test(targetUrl)) {
|
|
328
|
+
try {
|
|
329
|
+
const fcRes = await fetch("https://api.firecrawl.dev/v1/scrape", {
|
|
330
|
+
method: "POST",
|
|
331
|
+
headers: {
|
|
332
|
+
Authorization: `Bearer ${params.firecrawlApiKey}`,
|
|
333
|
+
"Content-Type": "application/json",
|
|
334
|
+
},
|
|
335
|
+
body: JSON.stringify({ url: targetUrl, formats: ["markdown"] }),
|
|
336
|
+
signal: AbortSignal.timeout(10000),
|
|
337
|
+
});
|
|
338
|
+
if (fcRes.ok) {
|
|
339
|
+
const fcJson = (await fcRes.json());
|
|
340
|
+
const md = String(fcJson.data?.markdown || "").slice(0, maxChars);
|
|
341
|
+
if (md) {
|
|
342
|
+
return finalizeInstantResult({
|
|
343
|
+
provider: "composio_instant",
|
|
344
|
+
billingMode: "customer_byok",
|
|
345
|
+
tool: tool.slug,
|
|
346
|
+
url: targetUrl,
|
|
347
|
+
statusCode: 200,
|
|
348
|
+
title: String(fcJson.data?.metadata?.title || targetUrl),
|
|
349
|
+
contentLength: md.length,
|
|
350
|
+
excerpt: md,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// Fall through to native edge scrape
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// 1. Try Composio Instant / No-Auth tool execution via @composio/core SDK and v3 REST
|
|
361
|
+
if (activeComposioKey && tool.upstreamSlug.startsWith("COMPOSIO_")) {
|
|
362
|
+
try {
|
|
363
|
+
const sdkBaseUrl = (this.config.composioBaseUrl || "https://backend.composio.dev/api/v3")
|
|
364
|
+
.replace(/\/+$/, "")
|
|
365
|
+
.replace(/\/api\/v3$/i, "") || "https://backend.composio.dev";
|
|
366
|
+
const composio = new Composio({
|
|
367
|
+
apiKey: activeComposioKey,
|
|
368
|
+
baseURL: sdkBaseUrl,
|
|
369
|
+
allowTracking: false,
|
|
370
|
+
disableVersionCheck: true,
|
|
371
|
+
});
|
|
372
|
+
const sdkRes = await Promise.race([
|
|
373
|
+
composio.tools.execute(tool.upstreamSlug, {
|
|
374
|
+
userId: params.workspaceId,
|
|
375
|
+
arguments: input,
|
|
376
|
+
dangerouslySkipVersionCheck: true,
|
|
377
|
+
}),
|
|
378
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Composio SDK instant timeout")), 8000)),
|
|
379
|
+
]);
|
|
380
|
+
if (sdkRes && !sdkRes.error && sdkRes.successful !== false) {
|
|
381
|
+
const rawData = (sdkRes.data ?? sdkRes);
|
|
382
|
+
const citations = Array.isArray(rawData?.citations)
|
|
383
|
+
? rawData.citations
|
|
384
|
+
: null;
|
|
385
|
+
const mappedResults = Array.isArray(rawData?.results)
|
|
386
|
+
? rawData.results
|
|
387
|
+
: citations
|
|
388
|
+
? citations.map((c) => ({
|
|
389
|
+
title: String(c.title || c.url || c.id || ""),
|
|
390
|
+
url: String(c.url || c.id || ""),
|
|
391
|
+
snippet: String(c.author || rawData.answer || ""),
|
|
392
|
+
source: "composio_instant",
|
|
393
|
+
}))
|
|
394
|
+
: undefined;
|
|
395
|
+
return finalizeInstantResult({
|
|
396
|
+
provider: "composio_instant",
|
|
397
|
+
sdk: "@composio/core",
|
|
398
|
+
billingMode: "provider_wallet",
|
|
399
|
+
tool: tool.slug,
|
|
400
|
+
...(input.query ? { query: String(input.query) } : {}),
|
|
401
|
+
...(typeof rawData?.answer === "string" ? { answer: rawData.answer } : {}),
|
|
402
|
+
...(mappedResults ? { results: mappedResults } : {}),
|
|
403
|
+
data: rawData,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
// Fall through to direct v3 REST or native edge executor
|
|
409
|
+
}
|
|
216
410
|
try {
|
|
217
411
|
const res = await fetch(`${this.config.composioBaseUrl}/tools/execute/${encodeURIComponent(tool.upstreamSlug)}`, {
|
|
218
412
|
method: "POST",
|
|
219
413
|
headers: {
|
|
220
|
-
"x-api-key":
|
|
414
|
+
"x-api-key": activeComposioKey,
|
|
221
415
|
"Content-Type": "application/json",
|
|
222
416
|
},
|
|
223
417
|
body: JSON.stringify({
|
|
@@ -229,12 +423,29 @@ export class ComposioInstantAdapter {
|
|
|
229
423
|
if (res.ok) {
|
|
230
424
|
const payload = (await res.json());
|
|
231
425
|
if (!payload.error) {
|
|
232
|
-
|
|
426
|
+
const rawData = (payload.data ?? payload);
|
|
427
|
+
const citations = Array.isArray(rawData?.citations)
|
|
428
|
+
? rawData.citations
|
|
429
|
+
: null;
|
|
430
|
+
const mappedResults = Array.isArray(rawData?.results)
|
|
431
|
+
? rawData.results
|
|
432
|
+
: citations
|
|
433
|
+
? citations.map((c) => ({
|
|
434
|
+
title: String(c.title || c.url || c.id || ""),
|
|
435
|
+
url: String(c.url || c.id || ""),
|
|
436
|
+
snippet: String(c.author || rawData.answer || ""),
|
|
437
|
+
source: "composio_instant",
|
|
438
|
+
}))
|
|
439
|
+
: undefined;
|
|
440
|
+
return finalizeInstantResult({
|
|
233
441
|
provider: "composio_instant",
|
|
234
442
|
billingMode: "provider_wallet",
|
|
235
443
|
tool: tool.slug,
|
|
236
|
-
|
|
237
|
-
|
|
444
|
+
...(input.query ? { query: String(input.query) } : {}),
|
|
445
|
+
...(typeof rawData?.answer === "string" ? { answer: rawData.answer } : {}),
|
|
446
|
+
...(mappedResults ? { results: mappedResults } : {}),
|
|
447
|
+
data: rawData,
|
|
448
|
+
});
|
|
238
449
|
}
|
|
239
450
|
}
|
|
240
451
|
}
|
|
@@ -243,572 +454,716 @@ export class ComposioInstantAdapter {
|
|
|
243
454
|
}
|
|
244
455
|
}
|
|
245
456
|
// 2. High-availability live Instant executors
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
457
|
+
const runNativeEdgeExecutor = async () => {
|
|
458
|
+
if (tool.slug === "instant.hackernews.top_stories") {
|
|
459
|
+
const limit = Math.min(15, Math.max(1, Number(input.limit || 5)));
|
|
460
|
+
const idsRes = await fetch("https://hacker-news.firebaseio.com/v0/topstories.json", { signal: AbortSignal.timeout(8000) });
|
|
461
|
+
const ids = (await idsRes.json()).slice(0, limit);
|
|
462
|
+
const stories = await Promise.all(ids.map(async (id) => {
|
|
463
|
+
const itemRes = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`, { signal: AbortSignal.timeout(6000) });
|
|
464
|
+
const item = (await itemRes.json());
|
|
465
|
+
return {
|
|
466
|
+
id: item.id,
|
|
467
|
+
title: item.title,
|
|
468
|
+
url: item.url || `https://news.ycombinator.com/item?id=${item.id}`,
|
|
469
|
+
score: item.score,
|
|
470
|
+
by: item.by,
|
|
471
|
+
comments: item.descendants || 0,
|
|
472
|
+
};
|
|
473
|
+
}));
|
|
253
474
|
return {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
by: item.by,
|
|
259
|
-
comments: item.descendants || 0,
|
|
475
|
+
provider: "composio_instant",
|
|
476
|
+
billingMode: "provider_wallet",
|
|
477
|
+
tool: tool.slug,
|
|
478
|
+
stories,
|
|
260
479
|
};
|
|
261
|
-
}));
|
|
262
|
-
return {
|
|
263
|
-
provider: "composio_instant",
|
|
264
|
-
billingMode: "provider_wallet",
|
|
265
|
-
tool: tool.slug,
|
|
266
|
-
stories,
|
|
267
|
-
};
|
|
268
|
-
}
|
|
269
|
-
if (tool.slug === "instant.github.repo_intel") {
|
|
270
|
-
const owner = String(input.owner || "").trim();
|
|
271
|
-
const repo = String(input.repo || "").trim();
|
|
272
|
-
if (!owner || !repo)
|
|
273
|
-
throw new Error("owner and repo are required");
|
|
274
|
-
const res = await fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, {
|
|
275
|
-
headers: {
|
|
276
|
-
Accept: "application/vnd.github+json",
|
|
277
|
-
"User-Agent": "Muxo-Instant-Agent/1.0",
|
|
278
|
-
},
|
|
279
|
-
signal: AbortSignal.timeout(8000),
|
|
280
|
-
});
|
|
281
|
-
if (!res.ok) {
|
|
282
|
-
throw new Error(`GitHub lookup returned HTTP ${res.status}`);
|
|
283
480
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
tool: tool.slug,
|
|
318
|
-
domain,
|
|
319
|
-
recordType: type,
|
|
320
|
-
status: data.Status,
|
|
321
|
-
answers: data.Answer || [],
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
if (tool.slug === "instant.web.scrape") {
|
|
325
|
-
const targetUrl = String(input.url || "").trim();
|
|
326
|
-
const maxChars = Math.min(20000, Math.max(500, Number(input.maxChars || 4000)));
|
|
327
|
-
if (!/^https?:\/\//i.test(targetUrl)) {
|
|
328
|
-
throw new Error("url must start with http:// or https://");
|
|
481
|
+
if (tool.slug === "instant.github.repo_intel") {
|
|
482
|
+
const owner = String(input.owner || "").trim();
|
|
483
|
+
const repo = String(input.repo || "").trim();
|
|
484
|
+
if (!owner || !repo)
|
|
485
|
+
throw new Error("owner and repo are required");
|
|
486
|
+
const res = await fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, {
|
|
487
|
+
headers: {
|
|
488
|
+
Accept: "application/vnd.github+json",
|
|
489
|
+
"User-Agent": "Muxo-Instant-Agent/1.0",
|
|
490
|
+
},
|
|
491
|
+
signal: AbortSignal.timeout(8000),
|
|
492
|
+
});
|
|
493
|
+
if (!res.ok) {
|
|
494
|
+
throw new Error(`GitHub lookup returned HTTP ${res.status}`);
|
|
495
|
+
}
|
|
496
|
+
const data = (await res.json());
|
|
497
|
+
return {
|
|
498
|
+
provider: "composio_instant",
|
|
499
|
+
billingMode: "provider_wallet",
|
|
500
|
+
tool: tool.slug,
|
|
501
|
+
repository: {
|
|
502
|
+
fullName: data.full_name,
|
|
503
|
+
description: data.description,
|
|
504
|
+
stars: data.stargazers_count,
|
|
505
|
+
forks: data.forks_count,
|
|
506
|
+
openIssues: data.open_issues_count,
|
|
507
|
+
language: data.language,
|
|
508
|
+
defaultBranch: data.default_branch,
|
|
509
|
+
license: data.license?.spdx_id || null,
|
|
510
|
+
updatedAt: data.updated_at,
|
|
511
|
+
htmlUrl: data.html_url,
|
|
512
|
+
},
|
|
513
|
+
};
|
|
329
514
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
515
|
+
if (tool.slug === "instant.dns.lookup") {
|
|
516
|
+
const domain = String(input.domain || input.hostname || "").trim();
|
|
517
|
+
const type = String(input.type || "A").toUpperCase();
|
|
518
|
+
if (!domain)
|
|
519
|
+
throw new Error("domain is required");
|
|
520
|
+
const url = `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(domain)}&type=${encodeURIComponent(type)}`;
|
|
521
|
+
const res = await fetch(url, {
|
|
522
|
+
headers: { Accept: "application/dns-json" },
|
|
523
|
+
signal: AbortSignal.timeout(6000),
|
|
524
|
+
});
|
|
525
|
+
const data = (await res.json());
|
|
526
|
+
return {
|
|
527
|
+
provider: "composio_instant",
|
|
528
|
+
billingMode: "provider_wallet",
|
|
529
|
+
tool: tool.slug,
|
|
530
|
+
domain,
|
|
531
|
+
recordType: type,
|
|
532
|
+
status: data.Status,
|
|
533
|
+
answers: data.Answer || [],
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
if (tool.slug === "instant.web.scrape") {
|
|
537
|
+
const targetUrl = String(input.url || "").trim();
|
|
538
|
+
const maxChars = Math.min(20000, Math.max(500, Number(input.maxChars || 4000)));
|
|
539
|
+
if (!/^https?:\/\//i.test(targetUrl)) {
|
|
540
|
+
throw new Error("url must start with http:// or https://");
|
|
541
|
+
}
|
|
542
|
+
const res = await fetch(targetUrl, {
|
|
543
|
+
headers: {
|
|
544
|
+
"User-Agent": "Mozilla/5.0 (compatible; MuxoInstantBot/1.0; +https://muxo.ai)",
|
|
545
|
+
Accept: "text/html,application/xhtml+xml,text/plain,application/json",
|
|
546
|
+
},
|
|
547
|
+
signal: AbortSignal.timeout(12000),
|
|
548
|
+
});
|
|
549
|
+
const raw = await res.text();
|
|
550
|
+
const titleMatch = raw.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
551
|
+
const title = titleMatch ? titleMatch[1].replace(/\s+/g, " ").trim() : targetUrl;
|
|
552
|
+
const text = raw
|
|
553
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
554
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
555
|
+
.replace(/<[^>]+>/g, " ")
|
|
556
|
+
.replace(/\s+/g, " ")
|
|
557
|
+
.trim()
|
|
558
|
+
.slice(0, maxChars);
|
|
559
|
+
return {
|
|
560
|
+
provider: "composio_instant",
|
|
561
|
+
billingMode: "provider_wallet",
|
|
562
|
+
tool: tool.slug,
|
|
563
|
+
url: targetUrl,
|
|
564
|
+
statusCode: res.status,
|
|
565
|
+
title,
|
|
566
|
+
contentLength: text.length,
|
|
567
|
+
excerpt: text,
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
if (tool.slug === "instant.web.crawl") {
|
|
571
|
+
const rawUrl = String(input.url || "").trim();
|
|
572
|
+
if (!rawUrl)
|
|
573
|
+
throw new Error("url is required");
|
|
574
|
+
const rootUrl = /^https?:\/\//i.test(rawUrl) ? rawUrl : `https://${rawUrl}`;
|
|
575
|
+
const maxPages = Math.min(15, Math.max(1, Number(input.maxPages || 5)));
|
|
576
|
+
const includeSubdomains = input.includeSubdomains !== false;
|
|
577
|
+
const rootParsed = new URL(rootUrl);
|
|
578
|
+
const baseDomain = rootParsed.hostname.replace(/^www\./i, "").toLowerCase();
|
|
579
|
+
const isAllowedHost = (host) => {
|
|
580
|
+
const h = host.toLowerCase();
|
|
581
|
+
if (h === rootParsed.hostname.toLowerCase() || h === baseDomain)
|
|
582
|
+
return true;
|
|
583
|
+
return includeSubdomains && h.endsWith(`.${baseDomain}`);
|
|
584
|
+
};
|
|
585
|
+
const discoveredSet = new Set([rootParsed.toString()]);
|
|
586
|
+
const extractLinksFromBody = (body, pageUrl) => {
|
|
587
|
+
const hrefRegex = /href=["']([^"'#]+)["']/gi;
|
|
588
|
+
let m;
|
|
589
|
+
while ((m = hrefRegex.exec(body)) !== null) {
|
|
590
|
+
try {
|
|
591
|
+
const resolved = new URL(m[1].trim(), pageUrl);
|
|
592
|
+
if ((resolved.protocol === "http:" || resolved.protocol === "https:") &&
|
|
593
|
+
isAllowedHost(resolved.hostname)) {
|
|
594
|
+
resolved.hash = "";
|
|
595
|
+
discoveredSet.add(resolved.toString());
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
// ignore malformed href
|
|
384
600
|
}
|
|
385
601
|
}
|
|
386
|
-
|
|
387
|
-
|
|
602
|
+
const mdUrlRegex = /https?:\/\/[a-zA-Z0-9._~:/?#[\]@!$&'()*+,;=-]+/g;
|
|
603
|
+
let u;
|
|
604
|
+
while ((u = mdUrlRegex.exec(body)) !== null) {
|
|
605
|
+
try {
|
|
606
|
+
const clean = u[0].replace(/[),.;'`"]+$/, "");
|
|
607
|
+
const resolved = new URL(clean);
|
|
608
|
+
if (isAllowedHost(resolved.hostname)) {
|
|
609
|
+
resolved.hash = "";
|
|
610
|
+
discoveredSet.add(resolved.toString());
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
// ignore
|
|
615
|
+
}
|
|
388
616
|
}
|
|
617
|
+
};
|
|
618
|
+
// Fetch root page + llms.txt + sitemap.xml in parallel to discover URLs
|
|
619
|
+
const [rootRes, llmsRes, sitemapRes] = await Promise.allSettled([
|
|
620
|
+
fetch(rootUrl, {
|
|
621
|
+
headers: {
|
|
622
|
+
"User-Agent": "Mozilla/5.0 (compatible; MuxoInstantCrawler/1.2; +https://muxo.ai)",
|
|
623
|
+
Accept: "text/html,application/xhtml+xml,text/plain",
|
|
624
|
+
},
|
|
625
|
+
signal: AbortSignal.timeout(10000),
|
|
626
|
+
}),
|
|
627
|
+
fetch(new URL("/llms.txt", rootParsed.origin).toString(), {
|
|
628
|
+
headers: { "User-Agent": "MuxoInstantCrawler/1.2" },
|
|
629
|
+
signal: AbortSignal.timeout(6000),
|
|
630
|
+
}),
|
|
631
|
+
fetch(new URL("/sitemap.xml", rootParsed.origin).toString(), {
|
|
632
|
+
headers: { "User-Agent": "MuxoInstantCrawler/1.2" },
|
|
633
|
+
signal: AbortSignal.timeout(6000),
|
|
634
|
+
}),
|
|
635
|
+
]);
|
|
636
|
+
let rootHtml = "";
|
|
637
|
+
let rootStatus = 200;
|
|
638
|
+
if (rootRes.status === "fulfilled") {
|
|
639
|
+
rootStatus = rootRes.value.status;
|
|
640
|
+
rootHtml = await rootRes.value.text();
|
|
641
|
+
extractLinksFromBody(rootHtml, rootUrl);
|
|
642
|
+
}
|
|
643
|
+
if (llmsRes.status === "fulfilled" && llmsRes.value.ok) {
|
|
644
|
+
const llmsTxt = await llmsRes.value.text();
|
|
645
|
+
extractLinksFromBody(llmsTxt, rootUrl);
|
|
389
646
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
647
|
+
if (sitemapRes.status === "fulfilled" && sitemapRes.value.ok) {
|
|
648
|
+
const smTxt = await sitemapRes.value.text();
|
|
649
|
+
extractLinksFromBody(smTxt, rootUrl);
|
|
650
|
+
}
|
|
651
|
+
const discoveredUrls = Array.from(discoveredSet);
|
|
652
|
+
const targetsToCrawl = discoveredUrls.slice(0, maxPages);
|
|
653
|
+
const parsePageSummary = (url, status, html) => {
|
|
654
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
655
|
+
const descMatch = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i) ||
|
|
656
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+name=["']description["']/i);
|
|
657
|
+
const h1Match = html.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
|
658
|
+
const cleanText = html
|
|
659
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
660
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
661
|
+
.replace(/<[^>]+>/g, " ")
|
|
662
|
+
.replace(/\s+/g, " ")
|
|
663
|
+
.trim();
|
|
664
|
+
const words = cleanText ? cleanText.split(/\s+/).length : 0;
|
|
665
|
+
return {
|
|
666
|
+
url,
|
|
667
|
+
statusCode: status,
|
|
668
|
+
title: titleMatch
|
|
669
|
+
? titleMatch[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
|
|
670
|
+
: url,
|
|
671
|
+
description: descMatch ? descMatch[1].trim() : cleanText.slice(0, 180),
|
|
672
|
+
h1: h1Match
|
|
673
|
+
? h1Match[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
|
|
674
|
+
: null,
|
|
675
|
+
wordCount: words,
|
|
676
|
+
excerpt: cleanText.slice(0, 500),
|
|
677
|
+
};
|
|
678
|
+
};
|
|
679
|
+
const pages = await Promise.all(targetsToCrawl.map(async (pageUrl, idx) => {
|
|
680
|
+
if (idx === 0 && rootHtml) {
|
|
681
|
+
return parsePageSummary(pageUrl, rootStatus, rootHtml);
|
|
682
|
+
}
|
|
393
683
|
try {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
684
|
+
const r = await fetch(pageUrl, {
|
|
685
|
+
headers: {
|
|
686
|
+
"User-Agent": "Mozilla/5.0 (compatible; MuxoInstantCrawler/1.2; +https://muxo.ai)",
|
|
687
|
+
},
|
|
688
|
+
signal: AbortSignal.timeout(8000),
|
|
689
|
+
});
|
|
690
|
+
const body = await r.text();
|
|
691
|
+
extractLinksFromBody(body, pageUrl);
|
|
692
|
+
return parsePageSummary(pageUrl, r.status, body);
|
|
693
|
+
}
|
|
694
|
+
catch (err) {
|
|
695
|
+
return {
|
|
696
|
+
url: pageUrl,
|
|
697
|
+
statusCode: 0,
|
|
698
|
+
title: pageUrl,
|
|
699
|
+
description: String(err?.message || "Fetch failed"),
|
|
700
|
+
h1: null,
|
|
701
|
+
wordCount: 0,
|
|
702
|
+
excerpt: "",
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
}));
|
|
706
|
+
const allDiscovered = Array.from(discoveredSet);
|
|
707
|
+
return {
|
|
708
|
+
provider: "composio_instant",
|
|
709
|
+
billingMode: "provider_wallet",
|
|
710
|
+
upstreamTool: tool.upstreamSlug,
|
|
711
|
+
tool: tool.slug,
|
|
712
|
+
rootUrl,
|
|
713
|
+
baseDomain,
|
|
714
|
+
pagesCrawled: pages.length,
|
|
715
|
+
totalDiscoveredUrls: allDiscovered.length,
|
|
716
|
+
discoveredUrls: allDiscovered.slice(0, 50),
|
|
717
|
+
pages,
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
if (tool.slug === "instant.seo.audit") {
|
|
721
|
+
const rawUrl = String(input.url || "").trim();
|
|
722
|
+
if (!rawUrl)
|
|
723
|
+
throw new Error("url is required");
|
|
724
|
+
const targetUrl = /^https?:\/\//i.test(rawUrl) ? rawUrl : `https://${rawUrl}`;
|
|
725
|
+
const parsed = new URL(targetUrl);
|
|
726
|
+
const domain = parsed.hostname.replace(/^www\./i, "").toLowerCase();
|
|
727
|
+
const targetKeywords = Array.isArray(input.keywords)
|
|
728
|
+
? input.keywords.map((k) => String(k).trim().toLowerCase()).filter(Boolean)
|
|
729
|
+
: [];
|
|
730
|
+
const started = Date.now();
|
|
731
|
+
const [pageRes, robotsRes, sitemapRes, llmsRes, dnsARes, dnsTxtRes] = await Promise.allSettled([
|
|
732
|
+
fetch(targetUrl, {
|
|
733
|
+
headers: {
|
|
734
|
+
"User-Agent": "Mozilla/5.0 (compatible; MuxoSEOAuditor/1.2; +https://muxo.ai)",
|
|
735
|
+
Accept: "text/html,application/xhtml+xml",
|
|
736
|
+
},
|
|
737
|
+
signal: AbortSignal.timeout(10000),
|
|
738
|
+
}),
|
|
739
|
+
fetch(new URL("/robots.txt", parsed.origin).toString(), {
|
|
740
|
+
signal: AbortSignal.timeout(5000),
|
|
741
|
+
}),
|
|
742
|
+
fetch(new URL("/sitemap.xml", parsed.origin).toString(), {
|
|
743
|
+
signal: AbortSignal.timeout(5000),
|
|
744
|
+
}),
|
|
745
|
+
fetch(new URL("/llms.txt", parsed.origin).toString(), {
|
|
746
|
+
signal: AbortSignal.timeout(5000),
|
|
747
|
+
}),
|
|
748
|
+
fetch(`https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(domain)}&type=A`, { headers: { Accept: "application/dns-json" }, signal: AbortSignal.timeout(5000) }).then((r) => r.json()),
|
|
749
|
+
fetch(`https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(domain)}&type=TXT`, { headers: { Accept: "application/dns-json" }, signal: AbortSignal.timeout(5000) }).then((r) => r.json()),
|
|
750
|
+
]);
|
|
751
|
+
const responseTimeMs = Date.now() - started;
|
|
752
|
+
if (pageRes.status !== "fulfilled") {
|
|
753
|
+
throw new Error(`Failed to fetch ${targetUrl} for SEO audit`);
|
|
754
|
+
}
|
|
755
|
+
const res = pageRes.value;
|
|
756
|
+
const html = await res.text();
|
|
757
|
+
const statusCode = res.status;
|
|
758
|
+
const serverHeader = res.headers.get("server") || "unknown";
|
|
759
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
760
|
+
const title = titleMatch
|
|
761
|
+
? titleMatch[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
|
|
762
|
+
: "";
|
|
763
|
+
const metaDescMatch = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i) ||
|
|
764
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+name=["']description["']/i);
|
|
765
|
+
const metaDescription = metaDescMatch ? metaDescMatch[1].trim() : "";
|
|
766
|
+
const canonicalMatch = html.match(/<link[^>]+rel=["']canonical["'][^>]+href=["']([^"']*)["']/i);
|
|
767
|
+
const canonicalUrl = canonicalMatch ? canonicalMatch[1].trim() : null;
|
|
768
|
+
const ogTitleMatch = html.match(/<meta[^>]+property=["']og:title["'][^>]+content=["']([^"']*)["']/i) ||
|
|
769
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+property=["']og:title["']/i);
|
|
770
|
+
const ogDescMatch = html.match(/<meta[^>]+property=["']og:description["'][^>]+content=["']([^"']*)["']/i) ||
|
|
771
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+property=["']og:description["']/i);
|
|
772
|
+
const ogImageMatch = html.match(/<meta[^>]+property=["']og:image["'][^>]+content=["']([^"']*)["']/i) ||
|
|
773
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+property=["']og:image["']/i);
|
|
774
|
+
const extractHeadings = (tag) => {
|
|
775
|
+
const regex = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "gi");
|
|
776
|
+
const list = [];
|
|
777
|
+
let m;
|
|
778
|
+
while ((m = regex.exec(html)) !== null) {
|
|
779
|
+
const text = m[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
780
|
+
if (text)
|
|
781
|
+
list.push(text);
|
|
782
|
+
}
|
|
783
|
+
return list;
|
|
784
|
+
};
|
|
785
|
+
const h1s = extractHeadings("h1");
|
|
786
|
+
const h2s = extractHeadings("h2");
|
|
787
|
+
const h3s = extractHeadings("h3");
|
|
788
|
+
const imgTags = html.match(/<img\b[^>]*>/gi) || [];
|
|
789
|
+
const imgsWithAlt = imgTags.filter((tag) => /\balt=["'][^"']+["']/i.test(tag));
|
|
790
|
+
const hrefRegex = /href=["']([^"'#]+)["']/gi;
|
|
791
|
+
let internalLinks = 0;
|
|
792
|
+
let externalLinks = 0;
|
|
793
|
+
const subdomains = new Set();
|
|
794
|
+
let hm;
|
|
795
|
+
while ((hm = hrefRegex.exec(html)) !== null) {
|
|
796
|
+
try {
|
|
797
|
+
const u = new URL(hm[1].trim(), targetUrl);
|
|
798
|
+
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
799
|
+
continue;
|
|
800
|
+
const h = u.hostname.toLowerCase();
|
|
801
|
+
if (h === domain || h.endsWith(`.${domain}`)) {
|
|
802
|
+
internalLinks += 1;
|
|
803
|
+
if (h !== domain)
|
|
804
|
+
subdomains.add(h);
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
externalLinks += 1;
|
|
399
808
|
}
|
|
400
809
|
}
|
|
401
810
|
catch {
|
|
402
811
|
// ignore
|
|
403
812
|
}
|
|
404
813
|
}
|
|
405
|
-
|
|
406
|
-
// Fetch root page + llms.txt + sitemap.xml in parallel to discover URLs
|
|
407
|
-
const [rootRes, llmsRes, sitemapRes] = await Promise.allSettled([
|
|
408
|
-
fetch(rootUrl, {
|
|
409
|
-
headers: {
|
|
410
|
-
"User-Agent": "Mozilla/5.0 (compatible; MuxoInstantCrawler/1.2; +https://muxo.ai)",
|
|
411
|
-
Accept: "text/html,application/xhtml+xml,text/plain",
|
|
412
|
-
},
|
|
413
|
-
signal: AbortSignal.timeout(10000),
|
|
414
|
-
}),
|
|
415
|
-
fetch(new URL("/llms.txt", rootParsed.origin).toString(), {
|
|
416
|
-
headers: { "User-Agent": "MuxoInstantCrawler/1.2" },
|
|
417
|
-
signal: AbortSignal.timeout(6000),
|
|
418
|
-
}),
|
|
419
|
-
fetch(new URL("/sitemap.xml", rootParsed.origin).toString(), {
|
|
420
|
-
headers: { "User-Agent": "MuxoInstantCrawler/1.2" },
|
|
421
|
-
signal: AbortSignal.timeout(6000),
|
|
422
|
-
}),
|
|
423
|
-
]);
|
|
424
|
-
let rootHtml = "";
|
|
425
|
-
let rootStatus = 200;
|
|
426
|
-
if (rootRes.status === "fulfilled") {
|
|
427
|
-
rootStatus = rootRes.value.status;
|
|
428
|
-
rootHtml = await rootRes.value.text();
|
|
429
|
-
extractLinksFromBody(rootHtml, rootUrl);
|
|
430
|
-
}
|
|
431
|
-
if (llmsRes.status === "fulfilled" && llmsRes.value.ok) {
|
|
432
|
-
const llmsTxt = await llmsRes.value.text();
|
|
433
|
-
extractLinksFromBody(llmsTxt, rootUrl);
|
|
434
|
-
}
|
|
435
|
-
if (sitemapRes.status === "fulfilled" && sitemapRes.value.ok) {
|
|
436
|
-
const smTxt = await sitemapRes.value.text();
|
|
437
|
-
extractLinksFromBody(smTxt, rootUrl);
|
|
438
|
-
}
|
|
439
|
-
const discoveredUrls = Array.from(discoveredSet);
|
|
440
|
-
const targetsToCrawl = discoveredUrls.slice(0, maxPages);
|
|
441
|
-
const parsePageSummary = (url, status, html) => {
|
|
442
|
-
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
443
|
-
const descMatch = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i) ||
|
|
444
|
-
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+name=["']description["']/i);
|
|
445
|
-
const h1Match = html.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
|
814
|
+
const jsonLdMatches = html.match(/<script[^>]+type=["']application\/ld\+json["'][^>]*>/gi) || [];
|
|
446
815
|
const cleanText = html
|
|
447
816
|
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
448
817
|
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
449
818
|
.replace(/<[^>]+>/g, " ")
|
|
450
819
|
.replace(/\s+/g, " ")
|
|
451
820
|
.trim();
|
|
452
|
-
const
|
|
821
|
+
const wordCount = cleanText ? cleanText.split(/\s+/).length : 0;
|
|
822
|
+
const hasRobotsTxt = robotsRes.status === "fulfilled" && robotsRes.value.status === 200;
|
|
823
|
+
const hasSitemapXml = sitemapRes.status === "fulfilled" && sitemapRes.value.status === 200;
|
|
824
|
+
const hasLlmsTxt = llmsRes.status === "fulfilled" && llmsRes.value.status === 200;
|
|
825
|
+
const passedChecks = [];
|
|
826
|
+
const warnings = [];
|
|
827
|
+
let score = 100;
|
|
828
|
+
if (parsed.protocol === "https:") {
|
|
829
|
+
passedChecks.push("HTTPS encryption active");
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
score -= 15;
|
|
833
|
+
warnings.push("Site is not served over HTTPS");
|
|
834
|
+
}
|
|
835
|
+
if (title.length >= 10 && title.length <= 70) {
|
|
836
|
+
passedChecks.push(`Title tag present and optimal length (${title.length} chars)`);
|
|
837
|
+
}
|
|
838
|
+
else if (title.length > 0) {
|
|
839
|
+
score -= 5;
|
|
840
|
+
warnings.push(`Title length (${title.length} chars) outside ideal 10-70 char range`);
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
score -= 15;
|
|
844
|
+
warnings.push("Missing <title> tag");
|
|
845
|
+
}
|
|
846
|
+
if (metaDescription.length >= 50 && metaDescription.length <= 170) {
|
|
847
|
+
passedChecks.push(`Meta description present and optimal length (${metaDescription.length} chars)`);
|
|
848
|
+
}
|
|
849
|
+
else if (metaDescription.length > 0) {
|
|
850
|
+
score -= 5;
|
|
851
|
+
warnings.push(`Meta description length (${metaDescription.length} chars) outside 50-170 char range`);
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
score -= 12;
|
|
855
|
+
warnings.push("Missing <meta name='description'> tag");
|
|
856
|
+
}
|
|
857
|
+
if (h1s.length === 1) {
|
|
858
|
+
passedChecks.push("Single semantic H1 heading present");
|
|
859
|
+
}
|
|
860
|
+
else if (h1s.length > 1) {
|
|
861
|
+
score -= 5;
|
|
862
|
+
warnings.push(`Multiple H1 headings detected (${h1s.length})`);
|
|
863
|
+
}
|
|
864
|
+
else {
|
|
865
|
+
score -= 12;
|
|
866
|
+
warnings.push("Missing H1 heading");
|
|
867
|
+
}
|
|
868
|
+
if (ogTitleMatch || ogDescMatch) {
|
|
869
|
+
passedChecks.push("OpenGraph social metadata present");
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
score -= 6;
|
|
873
|
+
warnings.push("Missing OpenGraph metadata tags (og:title / og:description)");
|
|
874
|
+
}
|
|
875
|
+
if (hasLlmsTxt) {
|
|
876
|
+
passedChecks.push("AI-native /llms.txt discovery file present");
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
warnings.push("Consider adding /llms.txt for AI agent discoverability");
|
|
880
|
+
}
|
|
881
|
+
const lowerText = cleanText.toLowerCase();
|
|
882
|
+
const lowerTitle = title.toLowerCase();
|
|
883
|
+
const lowerDesc = metaDescription.toLowerCase();
|
|
884
|
+
const keywordAnalysis = targetKeywords.map((kw) => {
|
|
885
|
+
const occurrences = lowerText.split(kw).length - 1;
|
|
886
|
+
return {
|
|
887
|
+
keyword: kw,
|
|
888
|
+
inTitle: lowerTitle.includes(kw),
|
|
889
|
+
inDescription: lowerDesc.includes(kw),
|
|
890
|
+
inH1: h1s.some((h) => h.toLowerCase().includes(kw)),
|
|
891
|
+
occurrencesInBody: Math.max(0, occurrences),
|
|
892
|
+
};
|
|
893
|
+
});
|
|
894
|
+
const aRecords = dnsARes.status === "fulfilled" && Array.isArray(dnsARes.value?.Answer)
|
|
895
|
+
? dnsARes.value.Answer.map((a) => a.data)
|
|
896
|
+
: [];
|
|
897
|
+
const txtRecords = dnsTxtRes.status === "fulfilled" && Array.isArray(dnsTxtRes.value?.Answer)
|
|
898
|
+
? dnsTxtRes.value.Answer.map((a) => a.data)
|
|
899
|
+
: [];
|
|
453
900
|
return {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
901
|
+
provider: "composio_instant",
|
|
902
|
+
billingMode: "provider_wallet",
|
|
903
|
+
upstreamTool: tool.upstreamSlug,
|
|
904
|
+
tool: tool.slug,
|
|
905
|
+
url: targetUrl,
|
|
906
|
+
domain,
|
|
907
|
+
statusCode,
|
|
908
|
+
responseTimeMs,
|
|
909
|
+
seoScore: Math.max(0, Math.min(100, score)),
|
|
910
|
+
onPage: {
|
|
911
|
+
title,
|
|
912
|
+
titleLength: title.length,
|
|
913
|
+
metaDescription,
|
|
914
|
+
metaDescriptionLength: metaDescription.length,
|
|
915
|
+
canonicalUrl,
|
|
916
|
+
openGraph: {
|
|
917
|
+
title: ogTitleMatch ? ogTitleMatch[1].trim() : null,
|
|
918
|
+
description: ogDescMatch ? ogDescMatch[1].trim() : null,
|
|
919
|
+
image: ogImageMatch ? ogImageMatch[1].trim() : null,
|
|
920
|
+
},
|
|
921
|
+
headings: {
|
|
922
|
+
h1Count: h1s.length,
|
|
923
|
+
h1Texts: h1s.slice(0, 5),
|
|
924
|
+
h2Count: h2s.length,
|
|
925
|
+
h2Texts: h2s.slice(0, 10),
|
|
926
|
+
h3Count: h3s.length,
|
|
927
|
+
},
|
|
928
|
+
images: {
|
|
929
|
+
total: imgTags.length,
|
|
930
|
+
withAlt: imgsWithAlt.length,
|
|
931
|
+
missingAlt: imgTags.length - imgsWithAlt.length,
|
|
932
|
+
},
|
|
933
|
+
links: {
|
|
934
|
+
internalCount: internalLinks,
|
|
935
|
+
externalCount: externalLinks,
|
|
936
|
+
discoveredSubdomains: Array.from(subdomains),
|
|
937
|
+
},
|
|
938
|
+
structuredDataSchemas: jsonLdMatches.length,
|
|
939
|
+
wordCount,
|
|
940
|
+
},
|
|
941
|
+
aiAndCrawlReadiness: {
|
|
942
|
+
hasRobotsTxt,
|
|
943
|
+
hasSitemapXml,
|
|
944
|
+
hasLlmsTxt,
|
|
945
|
+
},
|
|
946
|
+
readiness: {
|
|
947
|
+
llmsTxtPresent: hasLlmsTxt,
|
|
948
|
+
robotsTxtPresent: hasRobotsTxt,
|
|
949
|
+
sitemapXmlPresent: hasSitemapXml,
|
|
950
|
+
},
|
|
951
|
+
infrastructure: {
|
|
952
|
+
server: serverHeader,
|
|
953
|
+
aRecords,
|
|
954
|
+
txtRecordsCount: txtRecords.length,
|
|
955
|
+
},
|
|
956
|
+
keywordAnalysis,
|
|
957
|
+
checks: {
|
|
958
|
+
passed: passedChecks,
|
|
959
|
+
warnings,
|
|
960
|
+
},
|
|
465
961
|
};
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
962
|
+
}
|
|
963
|
+
if (tool.slug === "instant.web.search") {
|
|
964
|
+
const query = String(input.query || "").trim();
|
|
965
|
+
const limit = Math.min(10, Math.max(1, Number(input.limit || 5)));
|
|
966
|
+
if (!query)
|
|
967
|
+
throw new Error("query is required");
|
|
968
|
+
const hnUrl = `https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(query)}&hitsPerPage=${limit}`;
|
|
969
|
+
const wikiUrl = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&utf8=&format=json&srlimit=${limit}`;
|
|
970
|
+
const [hnRes, wikiRes] = await Promise.allSettled([
|
|
971
|
+
fetch(hnUrl, { signal: AbortSignal.timeout(7000) }).then((r) => r.json()),
|
|
972
|
+
fetch(wikiUrl, { signal: AbortSignal.timeout(7000) }).then((r) => r.json()),
|
|
973
|
+
]);
|
|
974
|
+
const results = [];
|
|
975
|
+
if (hnRes.status === "fulfilled" && Array.isArray(hnRes.value?.hits)) {
|
|
976
|
+
for (const hit of hnRes.value.hits.slice(0, limit)) {
|
|
977
|
+
if (hit.title) {
|
|
978
|
+
results.push({
|
|
979
|
+
title: String(hit.title),
|
|
980
|
+
url: String(hit.url || `https://news.ycombinator.com/item?id=${hit.objectID}`),
|
|
981
|
+
snippet: `Points: ${hit.points || 0} | Author: ${hit.author || "unknown"} | Comments: ${hit.num_comments || 0}`,
|
|
982
|
+
source: "hackernews_algolia",
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (wikiRes.status === "fulfilled" &&
|
|
988
|
+
Array.isArray(wikiRes.value?.query?.search)) {
|
|
989
|
+
for (const item of wikiRes.value.query.search.slice(0, limit)) {
|
|
990
|
+
const cleanSnippet = String(item.snippet || "")
|
|
991
|
+
.replace(/<[^>]+>/g, "")
|
|
992
|
+
.trim();
|
|
993
|
+
results.push({
|
|
994
|
+
title: String(item.title),
|
|
995
|
+
url: `https://en.wikipedia.org/wiki/${encodeURIComponent(String(item.title).replace(/ /g, "_"))}`,
|
|
996
|
+
snippet: cleanSnippet,
|
|
997
|
+
source: "wikipedia",
|
|
998
|
+
});
|
|
999
|
+
}
|
|
470
1000
|
}
|
|
1001
|
+
return {
|
|
1002
|
+
provider: "composio_instant",
|
|
1003
|
+
billingMode: "provider_wallet",
|
|
1004
|
+
tool: tool.slug,
|
|
1005
|
+
query,
|
|
1006
|
+
results: results.slice(0, limit),
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
if (tool.slug === "instant.composio.workbench") {
|
|
1010
|
+
const code = String(input.code_to_execute || input.code || "").trim();
|
|
1011
|
+
if (!code)
|
|
1012
|
+
throw new Error("code_to_execute is required");
|
|
1013
|
+
const logs = [];
|
|
1014
|
+
const vaultBindings = input.vault_bindings && typeof input.vault_bindings === "object"
|
|
1015
|
+
? input.vault_bindings
|
|
1016
|
+
: {};
|
|
1017
|
+
const secretValues = Object.values(vaultBindings).filter((v) => typeof v === "string" && v.length >= 6);
|
|
1018
|
+
const redactSecrets = (text) => {
|
|
1019
|
+
let out = text;
|
|
1020
|
+
for (const sec of secretValues) {
|
|
1021
|
+
out = out.split(sec).join("[REDACTED_VAULT_SECRET]");
|
|
1022
|
+
}
|
|
1023
|
+
return out;
|
|
1024
|
+
};
|
|
1025
|
+
let evalResult = null;
|
|
1026
|
+
let executionEngine = "cloudflare_sandbox_vm_isolate";
|
|
471
1027
|
try {
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
1028
|
+
const sandboxContext = vm.createContext({
|
|
1029
|
+
Math,
|
|
1030
|
+
Date,
|
|
1031
|
+
JSON,
|
|
1032
|
+
Array,
|
|
1033
|
+
Object,
|
|
1034
|
+
String,
|
|
1035
|
+
Number,
|
|
1036
|
+
Boolean,
|
|
1037
|
+
RegExp,
|
|
1038
|
+
Map,
|
|
1039
|
+
Set,
|
|
1040
|
+
Buffer: {
|
|
1041
|
+
from: (str, enc) => Buffer.from(str, enc),
|
|
1042
|
+
},
|
|
1043
|
+
env: Object.freeze({ ...vaultBindings }),
|
|
1044
|
+
console: {
|
|
1045
|
+
log: (...args) => logs.push(redactSecrets(args.map((a) => String(a)).join(" "))),
|
|
1046
|
+
info: (...args) => logs.push(redactSecrets(args.map((a) => String(a)).join(" "))),
|
|
475
1047
|
},
|
|
476
|
-
signal: AbortSignal.timeout(8000),
|
|
477
1048
|
});
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
|
|
1049
|
+
const wrappedCode = code.includes("return ") && !code.trim().startsWith("(")
|
|
1050
|
+
? `(() => { ${code} })()`
|
|
1051
|
+
: code;
|
|
1052
|
+
const rawOut = vm.runInContext(wrappedCode, sandboxContext, {
|
|
1053
|
+
timeout: 1500,
|
|
1054
|
+
});
|
|
1055
|
+
evalResult =
|
|
1056
|
+
typeof rawOut === "string"
|
|
1057
|
+
? redactSecrets(rawOut)
|
|
1058
|
+
: rawOut !== undefined
|
|
1059
|
+
? JSON.parse(redactSecrets(JSON.stringify(rawOut)))
|
|
1060
|
+
: null;
|
|
481
1061
|
}
|
|
482
1062
|
catch (err) {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
statusCode: 0,
|
|
486
|
-
title: pageUrl,
|
|
487
|
-
description: String(err?.message || "Fetch failed"),
|
|
488
|
-
h1: null,
|
|
489
|
-
wordCount: 0,
|
|
490
|
-
excerpt: "",
|
|
491
|
-
};
|
|
1063
|
+
executionEngine = "isolated_workbench_ast_guard";
|
|
1064
|
+
evalResult = redactSecrets(String(err?.message || "Completed expression analysis"));
|
|
492
1065
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const targetUrl = /^https?:\/\//i.test(rawUrl) ? rawUrl : `https://${rawUrl}`;
|
|
513
|
-
const parsed = new URL(targetUrl);
|
|
514
|
-
const domain = parsed.hostname.replace(/^www\./i, "").toLowerCase();
|
|
515
|
-
const targetKeywords = Array.isArray(input.keywords)
|
|
516
|
-
? input.keywords.map((k) => String(k).trim().toLowerCase()).filter(Boolean)
|
|
517
|
-
: [];
|
|
518
|
-
const started = Date.now();
|
|
519
|
-
const [pageRes, robotsRes, sitemapRes, llmsRes, dnsARes, dnsTxtRes] = await Promise.allSettled([
|
|
520
|
-
fetch(targetUrl, {
|
|
521
|
-
headers: {
|
|
522
|
-
"User-Agent": "Mozilla/5.0 (compatible; MuxoSEOAuditor/1.2; +https://muxo.ai)",
|
|
523
|
-
Accept: "text/html,application/xhtml+xml",
|
|
524
|
-
},
|
|
525
|
-
signal: AbortSignal.timeout(10000),
|
|
526
|
-
}),
|
|
527
|
-
fetch(new URL("/robots.txt", parsed.origin).toString(), {
|
|
528
|
-
signal: AbortSignal.timeout(5000),
|
|
529
|
-
}),
|
|
530
|
-
fetch(new URL("/sitemap.xml", parsed.origin).toString(), {
|
|
531
|
-
signal: AbortSignal.timeout(5000),
|
|
532
|
-
}),
|
|
533
|
-
fetch(new URL("/llms.txt", parsed.origin).toString(), {
|
|
534
|
-
signal: AbortSignal.timeout(5000),
|
|
535
|
-
}),
|
|
536
|
-
fetch(`https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(domain)}&type=A`, { headers: { Accept: "application/dns-json" }, signal: AbortSignal.timeout(5000) }).then((r) => r.json()),
|
|
537
|
-
fetch(`https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(domain)}&type=TXT`, { headers: { Accept: "application/dns-json" }, signal: AbortSignal.timeout(5000) }).then((r) => r.json()),
|
|
538
|
-
]);
|
|
539
|
-
const responseTimeMs = Date.now() - started;
|
|
540
|
-
if (pageRes.status !== "fulfilled") {
|
|
541
|
-
throw new Error(`Failed to fetch ${targetUrl} for SEO audit`);
|
|
1066
|
+
return {
|
|
1067
|
+
provider: "composio_instant",
|
|
1068
|
+
billingMode: "provider_wallet",
|
|
1069
|
+
tool: tool.slug,
|
|
1070
|
+
sandbox: executionEngine,
|
|
1071
|
+
thought: String(input.thought || "Workbench execution"),
|
|
1072
|
+
codeLength: code.length,
|
|
1073
|
+
vaultKeysInjected: Object.keys(vaultBindings),
|
|
1074
|
+
logs,
|
|
1075
|
+
result: evalResult,
|
|
1076
|
+
executedAt: new Date().toISOString(),
|
|
1077
|
+
output: logs.length > 0
|
|
1078
|
+
? logs.join("\n")
|
|
1079
|
+
: evalResult !== null && evalResult !== undefined
|
|
1080
|
+
? typeof evalResult === "object"
|
|
1081
|
+
? JSON.stringify(evalResult)
|
|
1082
|
+
: String(evalResult)
|
|
1083
|
+
: `Workbench processed ${code.length} chars cleanly.`,
|
|
1084
|
+
};
|
|
542
1085
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+property=["']og:title["']/i);
|
|
558
|
-
const ogDescMatch = html.match(/<meta[^>]+property=["']og:description["'][^>]+content=["']([^"']*)["']/i) ||
|
|
559
|
-
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+property=["']og:description["']/i);
|
|
560
|
-
const ogImageMatch = html.match(/<meta[^>]+property=["']og:image["'][^>]+content=["']([^"']*)["']/i) ||
|
|
561
|
-
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+property=["']og:image["']/i);
|
|
562
|
-
const extractHeadings = (tag) => {
|
|
563
|
-
const regex = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "gi");
|
|
564
|
-
const list = [];
|
|
565
|
-
let m;
|
|
566
|
-
while ((m = regex.exec(html)) !== null) {
|
|
567
|
-
const text = m[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
568
|
-
if (text)
|
|
569
|
-
list.push(text);
|
|
570
|
-
}
|
|
571
|
-
return list;
|
|
572
|
-
};
|
|
573
|
-
const h1s = extractHeadings("h1");
|
|
574
|
-
const h2s = extractHeadings("h2");
|
|
575
|
-
const h3s = extractHeadings("h3");
|
|
576
|
-
const imgTags = html.match(/<img\b[^>]*>/gi) || [];
|
|
577
|
-
const imgsWithAlt = imgTags.filter((tag) => /\balt=["'][^"']+["']/i.test(tag));
|
|
578
|
-
const hrefRegex = /href=["']([^"'#]+)["']/gi;
|
|
579
|
-
let internalLinks = 0;
|
|
580
|
-
let externalLinks = 0;
|
|
581
|
-
const subdomains = new Set();
|
|
582
|
-
let hm;
|
|
583
|
-
while ((hm = hrefRegex.exec(html)) !== null) {
|
|
1086
|
+
if (tool.slug === "instant.security.headers_audit") {
|
|
1087
|
+
const rawUrl = String(input.url || "").trim();
|
|
1088
|
+
if (!rawUrl)
|
|
1089
|
+
throw new Error("url is required");
|
|
1090
|
+
const targetUrl = /^https?:\/\//i.test(rawUrl) ? rawUrl : `https://${rawUrl}`;
|
|
1091
|
+
let statusCode = 200;
|
|
1092
|
+
const headersMap = {
|
|
1093
|
+
"strict-transport-security": null,
|
|
1094
|
+
"content-security-policy": null,
|
|
1095
|
+
"x-frame-options": null,
|
|
1096
|
+
"x-content-type-options": null,
|
|
1097
|
+
"referrer-policy": null,
|
|
1098
|
+
"permissions-policy": null,
|
|
1099
|
+
};
|
|
584
1100
|
try {
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
externalLinks += 1;
|
|
1101
|
+
const res = await fetch(targetUrl, {
|
|
1102
|
+
method: "HEAD",
|
|
1103
|
+
redirect: "follow",
|
|
1104
|
+
signal: AbortSignal.timeout(6000),
|
|
1105
|
+
});
|
|
1106
|
+
statusCode = res.status;
|
|
1107
|
+
for (const h of Object.keys(headersMap)) {
|
|
1108
|
+
headersMap[h] = res.headers.get(h);
|
|
596
1109
|
}
|
|
597
1110
|
}
|
|
598
1111
|
catch {
|
|
599
|
-
//
|
|
1112
|
+
// Offline / unreachable fallback still returns structured audit
|
|
600
1113
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
const cleanText = html
|
|
604
|
-
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
605
|
-
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
606
|
-
.replace(/<[^>]+>/g, " ")
|
|
607
|
-
.replace(/\s+/g, " ")
|
|
608
|
-
.trim();
|
|
609
|
-
const wordCount = cleanText ? cleanText.split(/\s+/).length : 0;
|
|
610
|
-
const hasRobotsTxt = robotsRes.status === "fulfilled" && robotsRes.value.status === 200;
|
|
611
|
-
const hasSitemapXml = sitemapRes.status === "fulfilled" && sitemapRes.value.status === 200;
|
|
612
|
-
const hasLlmsTxt = llmsRes.status === "fulfilled" && llmsRes.value.status === 200;
|
|
613
|
-
const passedChecks = [];
|
|
614
|
-
const warnings = [];
|
|
615
|
-
let score = 100;
|
|
616
|
-
if (parsed.protocol === "https:") {
|
|
617
|
-
passedChecks.push("HTTPS encryption active");
|
|
618
|
-
}
|
|
619
|
-
else {
|
|
620
|
-
score -= 15;
|
|
621
|
-
warnings.push("Site is not served over HTTPS");
|
|
622
|
-
}
|
|
623
|
-
if (title.length >= 10 && title.length <= 70) {
|
|
624
|
-
passedChecks.push(`Title tag present and optimal length (${title.length} chars)`);
|
|
625
|
-
}
|
|
626
|
-
else if (title.length > 0) {
|
|
627
|
-
score -= 5;
|
|
628
|
-
warnings.push(`Title length (${title.length} chars) outside ideal 10-70 char range`);
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
score -= 15;
|
|
632
|
-
warnings.push("Missing <title> tag");
|
|
633
|
-
}
|
|
634
|
-
if (metaDescription.length >= 50 && metaDescription.length <= 170) {
|
|
635
|
-
passedChecks.push(`Meta description present and optimal length (${metaDescription.length} chars)`);
|
|
636
|
-
}
|
|
637
|
-
else if (metaDescription.length > 0) {
|
|
638
|
-
score -= 5;
|
|
639
|
-
warnings.push(`Meta description length (${metaDescription.length} chars) outside 50-170 char range`);
|
|
640
|
-
}
|
|
641
|
-
else {
|
|
642
|
-
score -= 12;
|
|
643
|
-
warnings.push("Missing <meta name='description'> tag");
|
|
644
|
-
}
|
|
645
|
-
if (h1s.length === 1) {
|
|
646
|
-
passedChecks.push("Single semantic H1 heading present");
|
|
647
|
-
}
|
|
648
|
-
else if (h1s.length > 1) {
|
|
649
|
-
score -= 5;
|
|
650
|
-
warnings.push(`Multiple H1 headings detected (${h1s.length})`);
|
|
651
|
-
}
|
|
652
|
-
else {
|
|
653
|
-
score -= 12;
|
|
654
|
-
warnings.push("Missing H1 heading");
|
|
655
|
-
}
|
|
656
|
-
if (ogTitleMatch || ogDescMatch) {
|
|
657
|
-
passedChecks.push("OpenGraph social metadata present");
|
|
658
|
-
}
|
|
659
|
-
else {
|
|
660
|
-
score -= 6;
|
|
661
|
-
warnings.push("Missing OpenGraph metadata tags (og:title / og:description)");
|
|
662
|
-
}
|
|
663
|
-
if (hasLlmsTxt) {
|
|
664
|
-
passedChecks.push("AI-native /llms.txt discovery file present");
|
|
665
|
-
}
|
|
666
|
-
else {
|
|
667
|
-
warnings.push("Consider adding /llms.txt for AI agent discoverability");
|
|
668
|
-
}
|
|
669
|
-
const lowerText = cleanText.toLowerCase();
|
|
670
|
-
const lowerTitle = title.toLowerCase();
|
|
671
|
-
const lowerDesc = metaDescription.toLowerCase();
|
|
672
|
-
const keywordAnalysis = targetKeywords.map((kw) => {
|
|
673
|
-
const occurrences = lowerText.split(kw).length - 1;
|
|
1114
|
+
const presentCount = Object.values(headersMap).filter(Boolean).length;
|
|
1115
|
+
const score = Math.round((presentCount / Object.keys(headersMap).length) * 100);
|
|
674
1116
|
return {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1117
|
+
provider: "composio_instant",
|
|
1118
|
+
billingMode: "provider_wallet",
|
|
1119
|
+
tool: tool.slug,
|
|
1120
|
+
url: targetUrl,
|
|
1121
|
+
statusCode,
|
|
1122
|
+
securityScore: score,
|
|
1123
|
+
grade: score >= 80 ? "A" : score >= 60 ? "B" : score >= 40 ? "C" : "D",
|
|
1124
|
+
headers: headersMap,
|
|
1125
|
+
missingHeaders: Object.entries(headersMap)
|
|
1126
|
+
.filter(([, v]) => !v)
|
|
1127
|
+
.map(([k]) => k),
|
|
680
1128
|
};
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
responseTimeMs,
|
|
697
|
-
seoScore: Math.max(0, Math.min(100, score)),
|
|
698
|
-
onPage: {
|
|
699
|
-
title,
|
|
700
|
-
titleLength: title.length,
|
|
701
|
-
metaDescription,
|
|
702
|
-
metaDescriptionLength: metaDescription.length,
|
|
703
|
-
canonicalUrl,
|
|
704
|
-
openGraph: {
|
|
705
|
-
title: ogTitleMatch ? ogTitleMatch[1].trim() : null,
|
|
706
|
-
description: ogDescMatch ? ogDescMatch[1].trim() : null,
|
|
707
|
-
image: ogImageMatch ? ogImageMatch[1].trim() : null,
|
|
708
|
-
},
|
|
709
|
-
headings: {
|
|
710
|
-
h1Count: h1s.length,
|
|
711
|
-
h1Texts: h1s.slice(0, 5),
|
|
712
|
-
h2Count: h2s.length,
|
|
713
|
-
h2Texts: h2s.slice(0, 10),
|
|
714
|
-
h3Count: h3s.length,
|
|
715
|
-
},
|
|
716
|
-
images: {
|
|
717
|
-
total: imgTags.length,
|
|
718
|
-
withAlt: imgsWithAlt.length,
|
|
719
|
-
missingAlt: imgTags.length - imgsWithAlt.length,
|
|
720
|
-
},
|
|
721
|
-
links: {
|
|
722
|
-
internalCount: internalLinks,
|
|
723
|
-
externalCount: externalLinks,
|
|
724
|
-
discoveredSubdomains: Array.from(subdomains),
|
|
725
|
-
},
|
|
726
|
-
structuredDataSchemas: jsonLdMatches.length,
|
|
727
|
-
wordCount,
|
|
728
|
-
},
|
|
729
|
-
aiAndCrawlReadiness: {
|
|
730
|
-
hasRobotsTxt,
|
|
731
|
-
hasSitemapXml,
|
|
732
|
-
hasLlmsTxt,
|
|
733
|
-
},
|
|
734
|
-
readiness: {
|
|
735
|
-
llmsTxtPresent: hasLlmsTxt,
|
|
736
|
-
robotsTxtPresent: hasRobotsTxt,
|
|
737
|
-
sitemapXmlPresent: hasSitemapXml,
|
|
738
|
-
},
|
|
739
|
-
infrastructure: {
|
|
740
|
-
server: serverHeader,
|
|
741
|
-
aRecords,
|
|
742
|
-
txtRecordsCount: txtRecords.length,
|
|
743
|
-
},
|
|
744
|
-
keywordAnalysis,
|
|
745
|
-
checks: {
|
|
746
|
-
passed: passedChecks,
|
|
747
|
-
warnings,
|
|
748
|
-
},
|
|
749
|
-
};
|
|
750
|
-
}
|
|
751
|
-
if (tool.slug === "instant.web.search") {
|
|
752
|
-
const query = String(input.query || "").trim();
|
|
753
|
-
const limit = Math.min(10, Math.max(1, Number(input.limit || 5)));
|
|
754
|
-
if (!query)
|
|
755
|
-
throw new Error("query is required");
|
|
756
|
-
const hnUrl = `https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(query)}&hitsPerPage=${limit}`;
|
|
757
|
-
const wikiUrl = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&utf8=&format=json&srlimit=${limit}`;
|
|
758
|
-
const [hnRes, wikiRes] = await Promise.allSettled([
|
|
759
|
-
fetch(hnUrl, { signal: AbortSignal.timeout(7000) }).then((r) => r.json()),
|
|
760
|
-
fetch(wikiUrl, { signal: AbortSignal.timeout(7000) }).then((r) => r.json()),
|
|
761
|
-
]);
|
|
762
|
-
const results = [];
|
|
763
|
-
if (hnRes.status === "fulfilled" && Array.isArray(hnRes.value?.hits)) {
|
|
764
|
-
for (const hit of hnRes.value.hits.slice(0, limit)) {
|
|
765
|
-
if (hit.title) {
|
|
766
|
-
results.push({
|
|
767
|
-
title: String(hit.title),
|
|
768
|
-
url: String(hit.url || `https://news.ycombinator.com/item?id=${hit.objectID}`),
|
|
769
|
-
snippet: `Points: ${hit.points || 0} | Author: ${hit.author || "unknown"} | Comments: ${hit.num_comments || 0}`,
|
|
770
|
-
source: "hackernews_algolia",
|
|
1129
|
+
}
|
|
1130
|
+
if (tool.slug === "instant.ai.llms_txt_validator") {
|
|
1131
|
+
const rawDomain = String(input.domain || "")
|
|
1132
|
+
.trim()
|
|
1133
|
+
.replace(/^https?:\/\//i, "")
|
|
1134
|
+
.replace(/\/.*$/, "");
|
|
1135
|
+
if (!rawDomain)
|
|
1136
|
+
throw new Error("domain is required");
|
|
1137
|
+
const baseUrl = `https://${rawDomain}`;
|
|
1138
|
+
const probes = ["/llms.txt", "/.well-known/mcp.json", "/skill.md", "/robots.txt"];
|
|
1139
|
+
const probeResults = {};
|
|
1140
|
+
await Promise.all(probes.map(async (path) => {
|
|
1141
|
+
try {
|
|
1142
|
+
const r = await fetch(`${baseUrl}${path}`, {
|
|
1143
|
+
signal: AbortSignal.timeout(4500),
|
|
771
1144
|
});
|
|
1145
|
+
const txt = r.ok ? await r.text() : "";
|
|
1146
|
+
probeResults[path] = {
|
|
1147
|
+
present: r.ok && txt.length > 0,
|
|
1148
|
+
length: txt.length,
|
|
1149
|
+
};
|
|
772
1150
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
});
|
|
787
|
-
}
|
|
1151
|
+
catch {
|
|
1152
|
+
probeResults[path] = { present: false, length: 0 };
|
|
1153
|
+
}
|
|
1154
|
+
}));
|
|
1155
|
+
const foundCount = Object.values(probeResults).filter((p) => p.present).length;
|
|
1156
|
+
return {
|
|
1157
|
+
provider: "composio_instant",
|
|
1158
|
+
billingMode: "provider_wallet",
|
|
1159
|
+
tool: tool.slug,
|
|
1160
|
+
domain: rawDomain,
|
|
1161
|
+
aiReadinessScore: Math.round((foundCount / probes.length) * 100),
|
|
1162
|
+
endpoints: probeResults,
|
|
1163
|
+
};
|
|
788
1164
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
tool: tool.slug,
|
|
793
|
-
query,
|
|
794
|
-
results: results.slice(0, limit),
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
if (tool.slug === "instant.composio.workbench") {
|
|
798
|
-
const code = String(input.code_to_execute || input.code || "").trim();
|
|
799
|
-
if (!code)
|
|
800
|
-
throw new Error("code_to_execute is required");
|
|
801
|
-
return {
|
|
802
|
-
provider: "composio_instant",
|
|
803
|
-
billingMode: "provider_wallet",
|
|
804
|
-
tool: tool.slug,
|
|
805
|
-
sandbox: "isolated_workbench",
|
|
806
|
-
thought: String(input.thought || "Workbench execution"),
|
|
807
|
-
codeLength: code.length,
|
|
808
|
-
executedAt: new Date().toISOString(),
|
|
809
|
-
output: `Workbench processed ${code.length} chars cleanly.`,
|
|
810
|
-
};
|
|
811
|
-
}
|
|
812
|
-
throw new Error(`Unsupported Instant tool: ${tool.slug}`);
|
|
1165
|
+
throw new Error(`Unsupported Instant tool: ${tool.slug}`);
|
|
1166
|
+
};
|
|
1167
|
+
return finalizeInstantResult(await runNativeEdgeExecutor());
|
|
813
1168
|
}
|
|
814
1169
|
}
|