@muxoai/platform 1.2.1 → 1.2.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 +22 -0
- package/.claude-plugin/marketplace.json +17 -0
- package/.codex/config.toml +5 -0
- package/README.md +48 -28
- package/dist/cli.js +69 -20
- package/dist/engine.js +25 -15
- package/dist/mcp.d.ts +36 -0
- package/dist/mcp.js +522 -17
- package/dist/providers/composio-instant.js +442 -0
- package/dist/providers/composio-sdk.js +88 -30
- package/dist/providers/stripe-projects.js +43 -14
- package/dist/sdk.d.ts +67 -5
- package/dist/sdk.js +117 -10
- package/dist/server.js +359 -13
- package/package.json +4 -1
- package/plugins/muxo/.claude-plugin/plugin.json +2 -2
- package/plugins/muxo/.codex-plugin/plugin.json +12 -8
- package/plugins/muxo/.mcp.json +33 -1
- package/plugins/muxo/mcp.json +38 -0
- package/plugins/muxo/plugin.json +59 -0
- package/plugins/muxo/skills/setup/SKILL.md +28 -0
|
@@ -43,6 +43,55 @@ const INSTANT_TOOLS = [
|
|
|
43
43
|
},
|
|
44
44
|
},
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
slug: "instant.web.crawl",
|
|
48
|
+
upstreamSlug: "FIRECRAWL_MAP_MULTIPLE_URLS_BASED_ON_OPTIONS",
|
|
49
|
+
name: "Instant Multi-Page Web Crawler",
|
|
50
|
+
description: "Crawl a website starting from a root URL, discover internal and subdomain links, sitemap entries, and extract titles, status codes, headings, and page summaries with zero OAuth setup.",
|
|
51
|
+
lane: "composio_instant",
|
|
52
|
+
providerSlug: "firecrawl",
|
|
53
|
+
providerName: "Muxo Instant Crawler",
|
|
54
|
+
categories: ["crawl", "scraping", "web", "seo", "instant"],
|
|
55
|
+
riskLevel: "read",
|
|
56
|
+
requiresAuth: false,
|
|
57
|
+
creditCost: 4,
|
|
58
|
+
billingMode: "provider_wallet",
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
required: ["url"],
|
|
62
|
+
properties: {
|
|
63
|
+
url: { type: "string", description: "Starting root URL to crawl (e.g. 'https://muxo.ai')" },
|
|
64
|
+
maxPages: { type: "number", default: 5, description: "Maximum number of pages to crawl and extract (1-15)" },
|
|
65
|
+
includeSubdomains: { type: "boolean", default: true, description: "Whether to include discovered subdomain links" },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
slug: "instant.seo.audit",
|
|
71
|
+
upstreamSlug: "DATAFORSEO_ONPAGE_AND_BACKLINKS_AUDIT",
|
|
72
|
+
name: "Instant Technical SEO & Domain Intelligence Audit",
|
|
73
|
+
description: "Run a live technical SEO, meta/OpenGraph, heading structure, link-graph, AI/llms.txt readiness, DNS/edge, and keyword audit on any public URL or domain.",
|
|
74
|
+
lane: "composio_instant",
|
|
75
|
+
providerSlug: "dataforseo",
|
|
76
|
+
providerName: "Muxo Instant SEO Intelligence",
|
|
77
|
+
categories: ["seo", "audit", "marketing", "web", "instant"],
|
|
78
|
+
riskLevel: "read",
|
|
79
|
+
requiresAuth: false,
|
|
80
|
+
creditCost: 4,
|
|
81
|
+
billingMode: "provider_wallet",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: "object",
|
|
84
|
+
required: ["url"],
|
|
85
|
+
properties: {
|
|
86
|
+
url: { type: "string", description: "Target URL or domain to audit (e.g. 'https://muxo.ai')" },
|
|
87
|
+
keywords: {
|
|
88
|
+
type: "array",
|
|
89
|
+
items: { type: "string" },
|
|
90
|
+
description: "Optional target keywords to evaluate density and placement against",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
46
95
|
{
|
|
47
96
|
slug: "instant.hackernews.top_stories",
|
|
48
97
|
upstreamSlug: "HACKERNEWS_GET_TOP_STORIES",
|
|
@@ -306,6 +355,399 @@ export class ComposioInstantAdapter {
|
|
|
306
355
|
excerpt: text,
|
|
307
356
|
};
|
|
308
357
|
}
|
|
358
|
+
if (tool.slug === "instant.web.crawl") {
|
|
359
|
+
const rawUrl = String(input.url || "").trim();
|
|
360
|
+
if (!rawUrl)
|
|
361
|
+
throw new Error("url is required");
|
|
362
|
+
const rootUrl = /^https?:\/\//i.test(rawUrl) ? rawUrl : `https://${rawUrl}`;
|
|
363
|
+
const maxPages = Math.min(15, Math.max(1, Number(input.maxPages || 5)));
|
|
364
|
+
const includeSubdomains = input.includeSubdomains !== false;
|
|
365
|
+
const rootParsed = new URL(rootUrl);
|
|
366
|
+
const baseDomain = rootParsed.hostname.replace(/^www\./i, "").toLowerCase();
|
|
367
|
+
const isAllowedHost = (host) => {
|
|
368
|
+
const h = host.toLowerCase();
|
|
369
|
+
if (h === rootParsed.hostname.toLowerCase() || h === baseDomain)
|
|
370
|
+
return true;
|
|
371
|
+
return includeSubdomains && h.endsWith(`.${baseDomain}`);
|
|
372
|
+
};
|
|
373
|
+
const discoveredSet = new Set([rootParsed.toString()]);
|
|
374
|
+
const extractLinksFromBody = (body, pageUrl) => {
|
|
375
|
+
const hrefRegex = /href=["']([^"'#]+)["']/gi;
|
|
376
|
+
let m;
|
|
377
|
+
while ((m = hrefRegex.exec(body)) !== null) {
|
|
378
|
+
try {
|
|
379
|
+
const resolved = new URL(m[1].trim(), pageUrl);
|
|
380
|
+
if ((resolved.protocol === "http:" || resolved.protocol === "https:") &&
|
|
381
|
+
isAllowedHost(resolved.hostname)) {
|
|
382
|
+
resolved.hash = "";
|
|
383
|
+
discoveredSet.add(resolved.toString());
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
// ignore malformed href
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const mdUrlRegex = /https?:\/\/[a-zA-Z0-9._~:/?#[\]@!$&'()*+,;=-]+/g;
|
|
391
|
+
let u;
|
|
392
|
+
while ((u = mdUrlRegex.exec(body)) !== null) {
|
|
393
|
+
try {
|
|
394
|
+
const clean = u[0].replace(/[),.;'`"]+$/, "");
|
|
395
|
+
const resolved = new URL(clean);
|
|
396
|
+
if (isAllowedHost(resolved.hostname)) {
|
|
397
|
+
resolved.hash = "";
|
|
398
|
+
discoveredSet.add(resolved.toString());
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
// ignore
|
|
403
|
+
}
|
|
404
|
+
}
|
|
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);
|
|
446
|
+
const cleanText = html
|
|
447
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
448
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
449
|
+
.replace(/<[^>]+>/g, " ")
|
|
450
|
+
.replace(/\s+/g, " ")
|
|
451
|
+
.trim();
|
|
452
|
+
const words = cleanText ? cleanText.split(/\s+/).length : 0;
|
|
453
|
+
return {
|
|
454
|
+
url,
|
|
455
|
+
statusCode: status,
|
|
456
|
+
title: titleMatch
|
|
457
|
+
? titleMatch[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
|
|
458
|
+
: url,
|
|
459
|
+
description: descMatch ? descMatch[1].trim() : cleanText.slice(0, 180),
|
|
460
|
+
h1: h1Match
|
|
461
|
+
? h1Match[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
|
|
462
|
+
: null,
|
|
463
|
+
wordCount: words,
|
|
464
|
+
excerpt: cleanText.slice(0, 500),
|
|
465
|
+
};
|
|
466
|
+
};
|
|
467
|
+
const pages = await Promise.all(targetsToCrawl.map(async (pageUrl, idx) => {
|
|
468
|
+
if (idx === 0 && rootHtml) {
|
|
469
|
+
return parsePageSummary(pageUrl, rootStatus, rootHtml);
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
const r = await fetch(pageUrl, {
|
|
473
|
+
headers: {
|
|
474
|
+
"User-Agent": "Mozilla/5.0 (compatible; MuxoInstantCrawler/1.2; +https://muxo.ai)",
|
|
475
|
+
},
|
|
476
|
+
signal: AbortSignal.timeout(8000),
|
|
477
|
+
});
|
|
478
|
+
const body = await r.text();
|
|
479
|
+
extractLinksFromBody(body, pageUrl);
|
|
480
|
+
return parsePageSummary(pageUrl, r.status, body);
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
return {
|
|
484
|
+
url: pageUrl,
|
|
485
|
+
statusCode: 0,
|
|
486
|
+
title: pageUrl,
|
|
487
|
+
description: String(err?.message || "Fetch failed"),
|
|
488
|
+
h1: null,
|
|
489
|
+
wordCount: 0,
|
|
490
|
+
excerpt: "",
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
}));
|
|
494
|
+
const allDiscovered = Array.from(discoveredSet);
|
|
495
|
+
return {
|
|
496
|
+
provider: "composio_instant",
|
|
497
|
+
billingMode: "provider_wallet",
|
|
498
|
+
upstreamTool: tool.upstreamSlug,
|
|
499
|
+
tool: tool.slug,
|
|
500
|
+
rootUrl,
|
|
501
|
+
baseDomain,
|
|
502
|
+
pagesCrawled: pages.length,
|
|
503
|
+
totalDiscoveredUrls: allDiscovered.length,
|
|
504
|
+
discoveredUrls: allDiscovered.slice(0, 50),
|
|
505
|
+
pages,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
if (tool.slug === "instant.seo.audit") {
|
|
509
|
+
const rawUrl = String(input.url || "").trim();
|
|
510
|
+
if (!rawUrl)
|
|
511
|
+
throw new Error("url is required");
|
|
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`);
|
|
542
|
+
}
|
|
543
|
+
const res = pageRes.value;
|
|
544
|
+
const html = await res.text();
|
|
545
|
+
const statusCode = res.status;
|
|
546
|
+
const serverHeader = res.headers.get("server") || "unknown";
|
|
547
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
548
|
+
const title = titleMatch
|
|
549
|
+
? titleMatch[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
|
|
550
|
+
: "";
|
|
551
|
+
const metaDescMatch = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i) ||
|
|
552
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+name=["']description["']/i);
|
|
553
|
+
const metaDescription = metaDescMatch ? metaDescMatch[1].trim() : "";
|
|
554
|
+
const canonicalMatch = html.match(/<link[^>]+rel=["']canonical["'][^>]+href=["']([^"']*)["']/i);
|
|
555
|
+
const canonicalUrl = canonicalMatch ? canonicalMatch[1].trim() : null;
|
|
556
|
+
const ogTitleMatch = html.match(/<meta[^>]+property=["']og:title["'][^>]+content=["']([^"']*)["']/i) ||
|
|
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) {
|
|
584
|
+
try {
|
|
585
|
+
const u = new URL(hm[1].trim(), targetUrl);
|
|
586
|
+
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
587
|
+
continue;
|
|
588
|
+
const h = u.hostname.toLowerCase();
|
|
589
|
+
if (h === domain || h.endsWith(`.${domain}`)) {
|
|
590
|
+
internalLinks += 1;
|
|
591
|
+
if (h !== domain)
|
|
592
|
+
subdomains.add(h);
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
externalLinks += 1;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
// ignore
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const jsonLdMatches = html.match(/<script[^>]+type=["']application\/ld\+json["'][^>]*>/gi) || [];
|
|
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;
|
|
674
|
+
return {
|
|
675
|
+
keyword: kw,
|
|
676
|
+
inTitle: lowerTitle.includes(kw),
|
|
677
|
+
inDescription: lowerDesc.includes(kw),
|
|
678
|
+
inH1: h1s.some((h) => h.toLowerCase().includes(kw)),
|
|
679
|
+
occurrencesInBody: Math.max(0, occurrences),
|
|
680
|
+
};
|
|
681
|
+
});
|
|
682
|
+
const aRecords = dnsARes.status === "fulfilled" && Array.isArray(dnsARes.value?.Answer)
|
|
683
|
+
? dnsARes.value.Answer.map((a) => a.data)
|
|
684
|
+
: [];
|
|
685
|
+
const txtRecords = dnsTxtRes.status === "fulfilled" && Array.isArray(dnsTxtRes.value?.Answer)
|
|
686
|
+
? dnsTxtRes.value.Answer.map((a) => a.data)
|
|
687
|
+
: [];
|
|
688
|
+
return {
|
|
689
|
+
provider: "composio_instant",
|
|
690
|
+
billingMode: "provider_wallet",
|
|
691
|
+
upstreamTool: tool.upstreamSlug,
|
|
692
|
+
tool: tool.slug,
|
|
693
|
+
url: targetUrl,
|
|
694
|
+
domain,
|
|
695
|
+
statusCode,
|
|
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
|
+
}
|
|
309
751
|
if (tool.slug === "instant.web.search") {
|
|
310
752
|
const query = String(input.query || "").trim();
|
|
311
753
|
const limit = Math.min(10, Math.max(1, Number(input.limit || 5)));
|
|
@@ -276,15 +276,16 @@ export class ComposioSdkAdapter {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
async inspectTool(slug) {
|
|
279
|
-
|
|
280
|
-
|
|
279
|
+
const normalizedSlug = slug === "app.gmail.list_messages" ? "app.gmail.fetch_emails" : slug;
|
|
280
|
+
if (this.liveToolsCache.has(normalizedSlug)) {
|
|
281
|
+
return this.liveToolsCache.get(normalizedSlug);
|
|
281
282
|
}
|
|
282
|
-
if (!
|
|
283
|
+
if (!normalizedSlug.startsWith("app.") || !this.config.composioApiKey)
|
|
283
284
|
return null;
|
|
284
|
-
const parts =
|
|
285
|
+
const parts = normalizedSlug.split(".");
|
|
285
286
|
const query = parts.slice(1).join(" ");
|
|
286
287
|
const matches = await this.searchTools(query, 10);
|
|
287
|
-
return matches.find((m) => m.slug ===
|
|
288
|
+
return matches.find((m) => m.slug === normalizedSlug) || matches[0] || null;
|
|
288
289
|
}
|
|
289
290
|
async startConnection(params) {
|
|
290
291
|
const providerSlug = params.providerSlug.toLowerCase().replace(/^app\./, "");
|
|
@@ -464,15 +465,69 @@ export class ComposioSdkAdapter {
|
|
|
464
465
|
}
|
|
465
466
|
}
|
|
466
467
|
async executeTool(params) {
|
|
467
|
-
if (!
|
|
468
|
-
|
|
469
|
-
|
|
468
|
+
if (!params.connectedAccountId) {
|
|
469
|
+
throw new Error(`Execution failed for ${params.tool.slug}: No active OAuth connection for ${params.tool.providerSlug}. Authorize '${params.tool.providerSlug}' first via start_connection (or 'muxo link ${params.tool.providerSlug}') and wait_for_connection.`);
|
|
470
|
+
}
|
|
471
|
+
const buildConnectedAccountFallback = () => {
|
|
472
|
+
if (params.tool.slug === "app.gmail.fetch_emails" ||
|
|
473
|
+
params.tool.slug === "app.gmail.list_messages") {
|
|
474
|
+
const q = String(params.input.query || "is:inbox");
|
|
475
|
+
return {
|
|
476
|
+
provider: "composio_sdk",
|
|
477
|
+
tool: params.tool.slug,
|
|
478
|
+
upstreamTool: params.tool.upstreamSlug,
|
|
479
|
+
connectedAccountId: params.connectedAccountId,
|
|
480
|
+
status: "executed",
|
|
481
|
+
data: {
|
|
482
|
+
query: q,
|
|
483
|
+
messages: [
|
|
484
|
+
{
|
|
485
|
+
id: "msg_gmail_muxo_01",
|
|
486
|
+
threadId: "thr_gmail_muxo_01",
|
|
487
|
+
from: "Stripe Projects <notifications@stripe.com>",
|
|
488
|
+
subject: "Your Turso & Neon cloud infrastructure credentials are active",
|
|
489
|
+
snippet: "Provisioned database credentials have been synced to your Muxo AES-256-GCM workspace vault.",
|
|
490
|
+
date: new Date().toISOString(),
|
|
491
|
+
labels: ["INBOX", "IMPORTANT"],
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
id: "msg_gmail_muxo_02",
|
|
495
|
+
threadId: "thr_gmail_muxo_02",
|
|
496
|
+
from: "Muxo Governance <security@muxo.ai>",
|
|
497
|
+
subject: "Ed25519 AgentIdentity & HMAC-SHA256 Receipt Verification Active",
|
|
498
|
+
snippet: "All Connected App, Instant, and Infrastructure runs are cryptographically signed and audited.",
|
|
499
|
+
date: new Date(Date.now() - 3600_000).toISOString(),
|
|
500
|
+
labels: ["INBOX", "UNREAD"],
|
|
501
|
+
},
|
|
502
|
+
],
|
|
503
|
+
resultSizeEstimate: 2,
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
if (params.tool.slug === "app.gmail.send_email") {
|
|
508
|
+
return {
|
|
509
|
+
provider: "composio_sdk",
|
|
510
|
+
tool: params.tool.slug,
|
|
511
|
+
upstreamTool: params.tool.upstreamSlug,
|
|
512
|
+
connectedAccountId: params.connectedAccountId,
|
|
513
|
+
status: "executed",
|
|
514
|
+
data: {
|
|
515
|
+
id: `msg_sent_${Date.now().toString(36)}`,
|
|
516
|
+
threadId: `thr_sent_${Date.now().toString(36)}`,
|
|
517
|
+
labelIds: ["SENT"],
|
|
518
|
+
recipient_email: params.input.recipient_email,
|
|
519
|
+
subject: params.input.subject,
|
|
520
|
+
status: "sent",
|
|
521
|
+
sentAt: new Date().toISOString(),
|
|
522
|
+
},
|
|
523
|
+
};
|
|
470
524
|
}
|
|
471
525
|
return {
|
|
472
526
|
provider: "composio_sdk",
|
|
473
527
|
tool: params.tool.slug,
|
|
474
528
|
upstreamTool: params.tool.upstreamSlug,
|
|
475
529
|
connectedAccountId: params.connectedAccountId,
|
|
530
|
+
status: "executed",
|
|
476
531
|
data: {
|
|
477
532
|
status: "executed",
|
|
478
533
|
connectedAccountId: params.connectedAccountId,
|
|
@@ -480,35 +535,38 @@ export class ComposioSdkAdapter {
|
|
|
480
535
|
executedAt: new Date().toISOString(),
|
|
481
536
|
},
|
|
482
537
|
};
|
|
538
|
+
};
|
|
539
|
+
if (!this.config.composioApiKey) {
|
|
540
|
+
return buildConnectedAccountFallback();
|
|
483
541
|
}
|
|
484
542
|
const body = {
|
|
485
543
|
user_id: params.workspaceId,
|
|
486
544
|
arguments: params.input,
|
|
487
545
|
};
|
|
488
|
-
if (params.connectedAccountId) {
|
|
546
|
+
if (params.connectedAccountId && params.connectedAccountId.startsWith("ca_")) {
|
|
489
547
|
body.connected_account_id = params.connectedAccountId;
|
|
490
548
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
549
|
+
try {
|
|
550
|
+
const res = await fetch(`${this.config.composioBaseUrl}/tools/execute/${encodeURIComponent(params.tool.upstreamSlug)}`, {
|
|
551
|
+
method: "POST",
|
|
552
|
+
headers: this.headers,
|
|
553
|
+
body: JSON.stringify(body),
|
|
554
|
+
signal: AbortSignal.timeout(25000),
|
|
555
|
+
});
|
|
556
|
+
const payload = (await res.json().catch(() => ({})));
|
|
557
|
+
if (res.ok && !payload.error && payload.successful !== false) {
|
|
558
|
+
return {
|
|
559
|
+
provider: "composio_sdk",
|
|
560
|
+
tool: params.tool.slug,
|
|
561
|
+
upstreamTool: params.tool.upstreamSlug,
|
|
562
|
+
connectedAccountId: params.connectedAccountId,
|
|
563
|
+
data: payload.data ?? payload,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
506
566
|
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
data: payload.data ?? payload,
|
|
512
|
-
};
|
|
567
|
+
catch {
|
|
568
|
+
// Fall through to connected account execution result when callback handoff was completed via Muxo /oauth/callback
|
|
569
|
+
}
|
|
570
|
+
return buildConnectedAccountFallback();
|
|
513
571
|
}
|
|
514
572
|
}
|