@muxoai/platform 1.3.0 → 1.3.2
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 +1 -1
- package/.claude-plugin/marketplace.json +19 -4
- package/.codex/config.toml +9 -2
- package/README.md +63 -20
- package/dist/clerk.d.ts +33 -0
- package/dist/clerk.js +198 -0
- package/dist/cli.js +1181 -99
- package/dist/engine.d.ts +22 -1
- package/dist/engine.js +284 -14
- package/dist/mcp.d.ts +6 -1
- package/dist/mcp.js +109 -22
- package/dist/nl.js +60 -36
- package/dist/policy.d.ts +9 -0
- package/dist/policy.js +73 -0
- package/dist/providers/composio-instant.d.ts +4 -0
- package/dist/providers/composio-instant.js +634 -534
- package/dist/providers/composio-sdk.d.ts +1 -0
- package/dist/providers/composio-sdk.js +6 -2
- package/dist/providers/microsoft-governance.d.ts +61 -2
- package/dist/providers/microsoft-governance.js +458 -24
- package/dist/providers/stripe-billing.js +14 -4
- package/dist/providers/stripe-projects.d.ts +4 -1
- package/dist/providers/stripe-projects.js +123 -12
- package/dist/providers/supermemory.d.ts +28 -5
- package/dist/providers/supermemory.js +183 -55
- package/dist/sdk.d.ts +167 -1
- package/dist/sdk.js +113 -2
- package/dist/server.js +258 -40
- package/dist/store.d.ts +26 -1
- package/dist/store.js +255 -6
- package/dist/types.d.ts +86 -0
- package/dist/ui/console-html.d.ts +16 -0
- package/dist/ui/console-html.js +2427 -2206
- 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 +2 -2
- package/plugins/muxo/.app.json +1 -1
- package/plugins/muxo/.claude-plugin/plugin.json +2 -2
- package/plugins/muxo/.codex-plugin/plugin.json +7 -5
- package/plugins/muxo/.mcp.json +1 -33
- package/plugins/muxo/assets/logo.png +0 -0
- package/plugins/muxo/hooks/session-start.sh +1 -1
- package/plugins/muxo/plugin.json +63 -6
- package/plugins/muxo/skills/muxo/SKILL.md +92 -14
- package/plugins/muxo/skills/muxo/agents/openai.yaml +12 -0
- package/plugins/muxo/skills/muxo/references/cli.md +4 -2
- package/plugins/muxo/skills/muxo/references/mcp.md +15 -9
- package/plugins/muxo/skills/setup/SKILL.md +8 -2
- package/plugins/muxo/skills/setup/agents/openai.yaml +12 -0
|
@@ -163,11 +163,11 @@ const INSTANT_TOOLS = [
|
|
|
163
163
|
{
|
|
164
164
|
slug: "instant.composio.workbench",
|
|
165
165
|
upstreamSlug: "COMPOSIO_REMOTE_WORKBENCH",
|
|
166
|
-
name: "
|
|
167
|
-
description: "Execute isolated data processing, JSON transformation, or Python/JS analysis in
|
|
166
|
+
name: "Instant: Remote Sandbox Workbench",
|
|
167
|
+
description: "Execute isolated data processing, JSON transformation, or Python/JS analysis in Muxo's remote sandbox workbench without OAuth.",
|
|
168
168
|
lane: "composio_instant",
|
|
169
169
|
providerSlug: "composio",
|
|
170
|
-
providerName: "
|
|
170
|
+
providerName: "Muxo Workbench",
|
|
171
171
|
categories: ["sandbox", "compute", "analysis", "instant"],
|
|
172
172
|
riskLevel: "write",
|
|
173
173
|
requiresAuth: false,
|
|
@@ -211,13 +211,110 @@ export class ComposioInstantAdapter {
|
|
|
211
211
|
}
|
|
212
212
|
async executeTool(params) {
|
|
213
213
|
const { tool, input } = params;
|
|
214
|
+
const isMasterWalletEmpty = Boolean(params.masterWalletDepleted) ||
|
|
215
|
+
process.env.MUXO_MASTER_WALLET_DEPLETED === "true" ||
|
|
216
|
+
input._simulateMasterWalletEmpty === true;
|
|
217
|
+
const hasCustomerByok = Boolean((params.composioApiKey && params.composioApiKey.trim()) ||
|
|
218
|
+
(params.exaApiKey && params.exaApiKey.trim()) ||
|
|
219
|
+
(params.firecrawlApiKey && params.firecrawlApiKey.trim()));
|
|
220
|
+
const activeComposioKey = params.composioApiKey || (isMasterWalletEmpty ? null : this.config.composioApiKey);
|
|
221
|
+
const finalizeInstantResult = (res) => {
|
|
222
|
+
if (isMasterWalletEmpty && !hasCustomerByok) {
|
|
223
|
+
return {
|
|
224
|
+
...res,
|
|
225
|
+
masterWalletFailover: "zero_cost_native_edge",
|
|
226
|
+
fundingSource: "zero_cost_native_edge_failover",
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
if (hasCustomerByok) {
|
|
230
|
+
return {
|
|
231
|
+
...res,
|
|
232
|
+
fundingSource: "customer_byok",
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return res;
|
|
236
|
+
};
|
|
237
|
+
// 0. Try Customer BYOK Exa / Firecrawl keys if configured
|
|
238
|
+
if (tool.slug === "instant.web.search" && params.exaApiKey) {
|
|
239
|
+
const query = String(input.query || "").trim();
|
|
240
|
+
const limit = Math.min(10, Math.max(1, Number(input.limit || 5)));
|
|
241
|
+
if (query) {
|
|
242
|
+
try {
|
|
243
|
+
const exaRes = await fetch("https://api.exa.ai/search", {
|
|
244
|
+
method: "POST",
|
|
245
|
+
headers: {
|
|
246
|
+
"x-api-key": params.exaApiKey,
|
|
247
|
+
"Content-Type": "application/json",
|
|
248
|
+
},
|
|
249
|
+
body: JSON.stringify({ query, numResults: limit, type: "auto" }),
|
|
250
|
+
signal: AbortSignal.timeout(8000),
|
|
251
|
+
});
|
|
252
|
+
if (exaRes.ok) {
|
|
253
|
+
const exaJson = (await exaRes.json());
|
|
254
|
+
if (Array.isArray(exaJson.results) && exaJson.results.length > 0) {
|
|
255
|
+
return finalizeInstantResult({
|
|
256
|
+
provider: "composio_instant",
|
|
257
|
+
billingMode: "customer_byok",
|
|
258
|
+
tool: tool.slug,
|
|
259
|
+
query,
|
|
260
|
+
results: exaJson.results.slice(0, limit).map((r) => ({
|
|
261
|
+
title: String(r.title || r.url || ""),
|
|
262
|
+
url: String(r.url || ""),
|
|
263
|
+
snippet: String(r.text || r.highlight || `Author: ${r.author || "web"}`),
|
|
264
|
+
source: "exa_byok",
|
|
265
|
+
})),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Fall through to native edge search
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (tool.slug === "instant.web.scrape" && params.firecrawlApiKey) {
|
|
276
|
+
const targetUrl = String(input.url || "").trim();
|
|
277
|
+
const maxChars = Math.min(20000, Math.max(500, Number(input.maxChars || 4000)));
|
|
278
|
+
if (/^https?:\/\//i.test(targetUrl)) {
|
|
279
|
+
try {
|
|
280
|
+
const fcRes = await fetch("https://api.firecrawl.dev/v1/scrape", {
|
|
281
|
+
method: "POST",
|
|
282
|
+
headers: {
|
|
283
|
+
Authorization: `Bearer ${params.firecrawlApiKey}`,
|
|
284
|
+
"Content-Type": "application/json",
|
|
285
|
+
},
|
|
286
|
+
body: JSON.stringify({ url: targetUrl, formats: ["markdown"] }),
|
|
287
|
+
signal: AbortSignal.timeout(10000),
|
|
288
|
+
});
|
|
289
|
+
if (fcRes.ok) {
|
|
290
|
+
const fcJson = (await fcRes.json());
|
|
291
|
+
const md = String(fcJson.data?.markdown || "").slice(0, maxChars);
|
|
292
|
+
if (md) {
|
|
293
|
+
return finalizeInstantResult({
|
|
294
|
+
provider: "composio_instant",
|
|
295
|
+
billingMode: "customer_byok",
|
|
296
|
+
tool: tool.slug,
|
|
297
|
+
url: targetUrl,
|
|
298
|
+
statusCode: 200,
|
|
299
|
+
title: String(fcJson.data?.metadata?.title || targetUrl),
|
|
300
|
+
contentLength: md.length,
|
|
301
|
+
excerpt: md,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
// Fall through to native edge scrape
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
214
311
|
// 1. Try Composio Instant / No-Auth tool execution first if applicable
|
|
215
|
-
if (
|
|
312
|
+
if (activeComposioKey && tool.upstreamSlug.startsWith("COMPOSIO_")) {
|
|
216
313
|
try {
|
|
217
314
|
const res = await fetch(`${this.config.composioBaseUrl}/tools/execute/${encodeURIComponent(tool.upstreamSlug)}`, {
|
|
218
315
|
method: "POST",
|
|
219
316
|
headers: {
|
|
220
|
-
"x-api-key":
|
|
317
|
+
"x-api-key": activeComposioKey,
|
|
221
318
|
"Content-Type": "application/json",
|
|
222
319
|
},
|
|
223
320
|
body: JSON.stringify({
|
|
@@ -243,572 +340,575 @@ export class ComposioInstantAdapter {
|
|
|
243
340
|
}
|
|
244
341
|
}
|
|
245
342
|
// 2. High-availability live Instant executors
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
343
|
+
const runNativeEdgeExecutor = async () => {
|
|
344
|
+
if (tool.slug === "instant.hackernews.top_stories") {
|
|
345
|
+
const limit = Math.min(15, Math.max(1, Number(input.limit || 5)));
|
|
346
|
+
const idsRes = await fetch("https://hacker-news.firebaseio.com/v0/topstories.json", { signal: AbortSignal.timeout(8000) });
|
|
347
|
+
const ids = (await idsRes.json()).slice(0, limit);
|
|
348
|
+
const stories = await Promise.all(ids.map(async (id) => {
|
|
349
|
+
const itemRes = await fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`, { signal: AbortSignal.timeout(6000) });
|
|
350
|
+
const item = (await itemRes.json());
|
|
351
|
+
return {
|
|
352
|
+
id: item.id,
|
|
353
|
+
title: item.title,
|
|
354
|
+
url: item.url || `https://news.ycombinator.com/item?id=${item.id}`,
|
|
355
|
+
score: item.score,
|
|
356
|
+
by: item.by,
|
|
357
|
+
comments: item.descendants || 0,
|
|
358
|
+
};
|
|
359
|
+
}));
|
|
253
360
|
return {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
by: item.by,
|
|
259
|
-
comments: item.descendants || 0,
|
|
361
|
+
provider: "composio_instant",
|
|
362
|
+
billingMode: "provider_wallet",
|
|
363
|
+
tool: tool.slug,
|
|
364
|
+
stories,
|
|
260
365
|
};
|
|
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
366
|
}
|
|
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://");
|
|
367
|
+
if (tool.slug === "instant.github.repo_intel") {
|
|
368
|
+
const owner = String(input.owner || "").trim();
|
|
369
|
+
const repo = String(input.repo || "").trim();
|
|
370
|
+
if (!owner || !repo)
|
|
371
|
+
throw new Error("owner and repo are required");
|
|
372
|
+
const res = await fetch(`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, {
|
|
373
|
+
headers: {
|
|
374
|
+
Accept: "application/vnd.github+json",
|
|
375
|
+
"User-Agent": "Muxo-Instant-Agent/1.0",
|
|
376
|
+
},
|
|
377
|
+
signal: AbortSignal.timeout(8000),
|
|
378
|
+
});
|
|
379
|
+
if (!res.ok) {
|
|
380
|
+
throw new Error(`GitHub lookup returned HTTP ${res.status}`);
|
|
381
|
+
}
|
|
382
|
+
const data = (await res.json());
|
|
383
|
+
return {
|
|
384
|
+
provider: "composio_instant",
|
|
385
|
+
billingMode: "provider_wallet",
|
|
386
|
+
tool: tool.slug,
|
|
387
|
+
repository: {
|
|
388
|
+
fullName: data.full_name,
|
|
389
|
+
description: data.description,
|
|
390
|
+
stars: data.stargazers_count,
|
|
391
|
+
forks: data.forks_count,
|
|
392
|
+
openIssues: data.open_issues_count,
|
|
393
|
+
language: data.language,
|
|
394
|
+
defaultBranch: data.default_branch,
|
|
395
|
+
license: data.license?.spdx_id || null,
|
|
396
|
+
updatedAt: data.updated_at,
|
|
397
|
+
htmlUrl: data.html_url,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
329
400
|
}
|
|
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
|
-
|
|
401
|
+
if (tool.slug === "instant.dns.lookup") {
|
|
402
|
+
const domain = String(input.domain || input.hostname || "").trim();
|
|
403
|
+
const type = String(input.type || "A").toUpperCase();
|
|
404
|
+
if (!domain)
|
|
405
|
+
throw new Error("domain is required");
|
|
406
|
+
const url = `https://cloudflare-dns.com/dns-query?name=${encodeURIComponent(domain)}&type=${encodeURIComponent(type)}`;
|
|
407
|
+
const res = await fetch(url, {
|
|
408
|
+
headers: { Accept: "application/dns-json" },
|
|
409
|
+
signal: AbortSignal.timeout(6000),
|
|
410
|
+
});
|
|
411
|
+
const data = (await res.json());
|
|
412
|
+
return {
|
|
413
|
+
provider: "composio_instant",
|
|
414
|
+
billingMode: "provider_wallet",
|
|
415
|
+
tool: tool.slug,
|
|
416
|
+
domain,
|
|
417
|
+
recordType: type,
|
|
418
|
+
status: data.Status,
|
|
419
|
+
answers: data.Answer || [],
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
if (tool.slug === "instant.web.scrape") {
|
|
423
|
+
const targetUrl = String(input.url || "").trim();
|
|
424
|
+
const maxChars = Math.min(20000, Math.max(500, Number(input.maxChars || 4000)));
|
|
425
|
+
if (!/^https?:\/\//i.test(targetUrl)) {
|
|
426
|
+
throw new Error("url must start with http:// or https://");
|
|
427
|
+
}
|
|
428
|
+
const res = await fetch(targetUrl, {
|
|
429
|
+
headers: {
|
|
430
|
+
"User-Agent": "Mozilla/5.0 (compatible; MuxoInstantBot/1.0; +https://muxo.ai)",
|
|
431
|
+
Accept: "text/html,application/xhtml+xml,text/plain,application/json",
|
|
432
|
+
},
|
|
433
|
+
signal: AbortSignal.timeout(12000),
|
|
434
|
+
});
|
|
435
|
+
const raw = await res.text();
|
|
436
|
+
const titleMatch = raw.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
437
|
+
const title = titleMatch ? titleMatch[1].replace(/\s+/g, " ").trim() : targetUrl;
|
|
438
|
+
const text = raw
|
|
439
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
440
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
441
|
+
.replace(/<[^>]+>/g, " ")
|
|
442
|
+
.replace(/\s+/g, " ")
|
|
443
|
+
.trim()
|
|
444
|
+
.slice(0, maxChars);
|
|
445
|
+
return {
|
|
446
|
+
provider: "composio_instant",
|
|
447
|
+
billingMode: "provider_wallet",
|
|
448
|
+
tool: tool.slug,
|
|
449
|
+
url: targetUrl,
|
|
450
|
+
statusCode: res.status,
|
|
451
|
+
title,
|
|
452
|
+
contentLength: text.length,
|
|
453
|
+
excerpt: text,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (tool.slug === "instant.web.crawl") {
|
|
457
|
+
const rawUrl = String(input.url || "").trim();
|
|
458
|
+
if (!rawUrl)
|
|
459
|
+
throw new Error("url is required");
|
|
460
|
+
const rootUrl = /^https?:\/\//i.test(rawUrl) ? rawUrl : `https://${rawUrl}`;
|
|
461
|
+
const maxPages = Math.min(15, Math.max(1, Number(input.maxPages || 5)));
|
|
462
|
+
const includeSubdomains = input.includeSubdomains !== false;
|
|
463
|
+
const rootParsed = new URL(rootUrl);
|
|
464
|
+
const baseDomain = rootParsed.hostname.replace(/^www\./i, "").toLowerCase();
|
|
465
|
+
const isAllowedHost = (host) => {
|
|
466
|
+
const h = host.toLowerCase();
|
|
467
|
+
if (h === rootParsed.hostname.toLowerCase() || h === baseDomain)
|
|
468
|
+
return true;
|
|
469
|
+
return includeSubdomains && h.endsWith(`.${baseDomain}`);
|
|
470
|
+
};
|
|
471
|
+
const discoveredSet = new Set([rootParsed.toString()]);
|
|
472
|
+
const extractLinksFromBody = (body, pageUrl) => {
|
|
473
|
+
const hrefRegex = /href=["']([^"'#]+)["']/gi;
|
|
474
|
+
let m;
|
|
475
|
+
while ((m = hrefRegex.exec(body)) !== null) {
|
|
476
|
+
try {
|
|
477
|
+
const resolved = new URL(m[1].trim(), pageUrl);
|
|
478
|
+
if ((resolved.protocol === "http:" || resolved.protocol === "https:") &&
|
|
479
|
+
isAllowedHost(resolved.hostname)) {
|
|
480
|
+
resolved.hash = "";
|
|
481
|
+
discoveredSet.add(resolved.toString());
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
// ignore malformed href
|
|
384
486
|
}
|
|
385
487
|
}
|
|
386
|
-
|
|
387
|
-
|
|
488
|
+
const mdUrlRegex = /https?:\/\/[a-zA-Z0-9._~:/?#[\]@!$&'()*+,;=-]+/g;
|
|
489
|
+
let u;
|
|
490
|
+
while ((u = mdUrlRegex.exec(body)) !== null) {
|
|
491
|
+
try {
|
|
492
|
+
const clean = u[0].replace(/[),.;'`"]+$/, "");
|
|
493
|
+
const resolved = new URL(clean);
|
|
494
|
+
if (isAllowedHost(resolved.hostname)) {
|
|
495
|
+
resolved.hash = "";
|
|
496
|
+
discoveredSet.add(resolved.toString());
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
catch {
|
|
500
|
+
// ignore
|
|
501
|
+
}
|
|
388
502
|
}
|
|
503
|
+
};
|
|
504
|
+
// Fetch root page + llms.txt + sitemap.xml in parallel to discover URLs
|
|
505
|
+
const [rootRes, llmsRes, sitemapRes] = await Promise.allSettled([
|
|
506
|
+
fetch(rootUrl, {
|
|
507
|
+
headers: {
|
|
508
|
+
"User-Agent": "Mozilla/5.0 (compatible; MuxoInstantCrawler/1.2; +https://muxo.ai)",
|
|
509
|
+
Accept: "text/html,application/xhtml+xml,text/plain",
|
|
510
|
+
},
|
|
511
|
+
signal: AbortSignal.timeout(10000),
|
|
512
|
+
}),
|
|
513
|
+
fetch(new URL("/llms.txt", rootParsed.origin).toString(), {
|
|
514
|
+
headers: { "User-Agent": "MuxoInstantCrawler/1.2" },
|
|
515
|
+
signal: AbortSignal.timeout(6000),
|
|
516
|
+
}),
|
|
517
|
+
fetch(new URL("/sitemap.xml", rootParsed.origin).toString(), {
|
|
518
|
+
headers: { "User-Agent": "MuxoInstantCrawler/1.2" },
|
|
519
|
+
signal: AbortSignal.timeout(6000),
|
|
520
|
+
}),
|
|
521
|
+
]);
|
|
522
|
+
let rootHtml = "";
|
|
523
|
+
let rootStatus = 200;
|
|
524
|
+
if (rootRes.status === "fulfilled") {
|
|
525
|
+
rootStatus = rootRes.value.status;
|
|
526
|
+
rootHtml = await rootRes.value.text();
|
|
527
|
+
extractLinksFromBody(rootHtml, rootUrl);
|
|
528
|
+
}
|
|
529
|
+
if (llmsRes.status === "fulfilled" && llmsRes.value.ok) {
|
|
530
|
+
const llmsTxt = await llmsRes.value.text();
|
|
531
|
+
extractLinksFromBody(llmsTxt, rootUrl);
|
|
532
|
+
}
|
|
533
|
+
if (sitemapRes.status === "fulfilled" && sitemapRes.value.ok) {
|
|
534
|
+
const smTxt = await sitemapRes.value.text();
|
|
535
|
+
extractLinksFromBody(smTxt, rootUrl);
|
|
389
536
|
}
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
537
|
+
const discoveredUrls = Array.from(discoveredSet);
|
|
538
|
+
const targetsToCrawl = discoveredUrls.slice(0, maxPages);
|
|
539
|
+
const parsePageSummary = (url, status, html) => {
|
|
540
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
541
|
+
const descMatch = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i) ||
|
|
542
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+name=["']description["']/i);
|
|
543
|
+
const h1Match = html.match(/<h1[^>]*>([\s\S]*?)<\/h1>/i);
|
|
544
|
+
const cleanText = html
|
|
545
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
546
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
547
|
+
.replace(/<[^>]+>/g, " ")
|
|
548
|
+
.replace(/\s+/g, " ")
|
|
549
|
+
.trim();
|
|
550
|
+
const words = cleanText ? cleanText.split(/\s+/).length : 0;
|
|
551
|
+
return {
|
|
552
|
+
url,
|
|
553
|
+
statusCode: status,
|
|
554
|
+
title: titleMatch
|
|
555
|
+
? titleMatch[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
|
|
556
|
+
: url,
|
|
557
|
+
description: descMatch ? descMatch[1].trim() : cleanText.slice(0, 180),
|
|
558
|
+
h1: h1Match
|
|
559
|
+
? h1Match[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
|
|
560
|
+
: null,
|
|
561
|
+
wordCount: words,
|
|
562
|
+
excerpt: cleanText.slice(0, 500),
|
|
563
|
+
};
|
|
564
|
+
};
|
|
565
|
+
const pages = await Promise.all(targetsToCrawl.map(async (pageUrl, idx) => {
|
|
566
|
+
if (idx === 0 && rootHtml) {
|
|
567
|
+
return parsePageSummary(pageUrl, rootStatus, rootHtml);
|
|
568
|
+
}
|
|
393
569
|
try {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
570
|
+
const r = await fetch(pageUrl, {
|
|
571
|
+
headers: {
|
|
572
|
+
"User-Agent": "Mozilla/5.0 (compatible; MuxoInstantCrawler/1.2; +https://muxo.ai)",
|
|
573
|
+
},
|
|
574
|
+
signal: AbortSignal.timeout(8000),
|
|
575
|
+
});
|
|
576
|
+
const body = await r.text();
|
|
577
|
+
extractLinksFromBody(body, pageUrl);
|
|
578
|
+
return parsePageSummary(pageUrl, r.status, body);
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
return {
|
|
582
|
+
url: pageUrl,
|
|
583
|
+
statusCode: 0,
|
|
584
|
+
title: pageUrl,
|
|
585
|
+
description: String(err?.message || "Fetch failed"),
|
|
586
|
+
h1: null,
|
|
587
|
+
wordCount: 0,
|
|
588
|
+
excerpt: "",
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
}));
|
|
592
|
+
const allDiscovered = Array.from(discoveredSet);
|
|
593
|
+
return {
|
|
594
|
+
provider: "composio_instant",
|
|
595
|
+
billingMode: "provider_wallet",
|
|
596
|
+
upstreamTool: tool.upstreamSlug,
|
|
597
|
+
tool: tool.slug,
|
|
598
|
+
rootUrl,
|
|
599
|
+
baseDomain,
|
|
600
|
+
pagesCrawled: pages.length,
|
|
601
|
+
totalDiscoveredUrls: allDiscovered.length,
|
|
602
|
+
discoveredUrls: allDiscovered.slice(0, 50),
|
|
603
|
+
pages,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
if (tool.slug === "instant.seo.audit") {
|
|
607
|
+
const rawUrl = String(input.url || "").trim();
|
|
608
|
+
if (!rawUrl)
|
|
609
|
+
throw new Error("url is required");
|
|
610
|
+
const targetUrl = /^https?:\/\//i.test(rawUrl) ? rawUrl : `https://${rawUrl}`;
|
|
611
|
+
const parsed = new URL(targetUrl);
|
|
612
|
+
const domain = parsed.hostname.replace(/^www\./i, "").toLowerCase();
|
|
613
|
+
const targetKeywords = Array.isArray(input.keywords)
|
|
614
|
+
? input.keywords.map((k) => String(k).trim().toLowerCase()).filter(Boolean)
|
|
615
|
+
: [];
|
|
616
|
+
const started = Date.now();
|
|
617
|
+
const [pageRes, robotsRes, sitemapRes, llmsRes, dnsARes, dnsTxtRes] = await Promise.allSettled([
|
|
618
|
+
fetch(targetUrl, {
|
|
619
|
+
headers: {
|
|
620
|
+
"User-Agent": "Mozilla/5.0 (compatible; MuxoSEOAuditor/1.2; +https://muxo.ai)",
|
|
621
|
+
Accept: "text/html,application/xhtml+xml",
|
|
622
|
+
},
|
|
623
|
+
signal: AbortSignal.timeout(10000),
|
|
624
|
+
}),
|
|
625
|
+
fetch(new URL("/robots.txt", parsed.origin).toString(), {
|
|
626
|
+
signal: AbortSignal.timeout(5000),
|
|
627
|
+
}),
|
|
628
|
+
fetch(new URL("/sitemap.xml", parsed.origin).toString(), {
|
|
629
|
+
signal: AbortSignal.timeout(5000),
|
|
630
|
+
}),
|
|
631
|
+
fetch(new URL("/llms.txt", parsed.origin).toString(), {
|
|
632
|
+
signal: AbortSignal.timeout(5000),
|
|
633
|
+
}),
|
|
634
|
+
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()),
|
|
635
|
+
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()),
|
|
636
|
+
]);
|
|
637
|
+
const responseTimeMs = Date.now() - started;
|
|
638
|
+
if (pageRes.status !== "fulfilled") {
|
|
639
|
+
throw new Error(`Failed to fetch ${targetUrl} for SEO audit`);
|
|
640
|
+
}
|
|
641
|
+
const res = pageRes.value;
|
|
642
|
+
const html = await res.text();
|
|
643
|
+
const statusCode = res.status;
|
|
644
|
+
const serverHeader = res.headers.get("server") || "unknown";
|
|
645
|
+
const titleMatch = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
646
|
+
const title = titleMatch
|
|
647
|
+
? titleMatch[1].replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim()
|
|
648
|
+
: "";
|
|
649
|
+
const metaDescMatch = html.match(/<meta[^>]+name=["']description["'][^>]+content=["']([^"']*)["']/i) ||
|
|
650
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+name=["']description["']/i);
|
|
651
|
+
const metaDescription = metaDescMatch ? metaDescMatch[1].trim() : "";
|
|
652
|
+
const canonicalMatch = html.match(/<link[^>]+rel=["']canonical["'][^>]+href=["']([^"']*)["']/i);
|
|
653
|
+
const canonicalUrl = canonicalMatch ? canonicalMatch[1].trim() : null;
|
|
654
|
+
const ogTitleMatch = html.match(/<meta[^>]+property=["']og:title["'][^>]+content=["']([^"']*)["']/i) ||
|
|
655
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+property=["']og:title["']/i);
|
|
656
|
+
const ogDescMatch = html.match(/<meta[^>]+property=["']og:description["'][^>]+content=["']([^"']*)["']/i) ||
|
|
657
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+property=["']og:description["']/i);
|
|
658
|
+
const ogImageMatch = html.match(/<meta[^>]+property=["']og:image["'][^>]+content=["']([^"']*)["']/i) ||
|
|
659
|
+
html.match(/<meta[^>]+content=["']([^"']*)["'][^>]+property=["']og:image["']/i);
|
|
660
|
+
const extractHeadings = (tag) => {
|
|
661
|
+
const regex = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, "gi");
|
|
662
|
+
const list = [];
|
|
663
|
+
let m;
|
|
664
|
+
while ((m = regex.exec(html)) !== null) {
|
|
665
|
+
const text = m[1].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
666
|
+
if (text)
|
|
667
|
+
list.push(text);
|
|
668
|
+
}
|
|
669
|
+
return list;
|
|
670
|
+
};
|
|
671
|
+
const h1s = extractHeadings("h1");
|
|
672
|
+
const h2s = extractHeadings("h2");
|
|
673
|
+
const h3s = extractHeadings("h3");
|
|
674
|
+
const imgTags = html.match(/<img\b[^>]*>/gi) || [];
|
|
675
|
+
const imgsWithAlt = imgTags.filter((tag) => /\balt=["'][^"']+["']/i.test(tag));
|
|
676
|
+
const hrefRegex = /href=["']([^"'#]+)["']/gi;
|
|
677
|
+
let internalLinks = 0;
|
|
678
|
+
let externalLinks = 0;
|
|
679
|
+
const subdomains = new Set();
|
|
680
|
+
let hm;
|
|
681
|
+
while ((hm = hrefRegex.exec(html)) !== null) {
|
|
682
|
+
try {
|
|
683
|
+
const u = new URL(hm[1].trim(), targetUrl);
|
|
684
|
+
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
685
|
+
continue;
|
|
686
|
+
const h = u.hostname.toLowerCase();
|
|
687
|
+
if (h === domain || h.endsWith(`.${domain}`)) {
|
|
688
|
+
internalLinks += 1;
|
|
689
|
+
if (h !== domain)
|
|
690
|
+
subdomains.add(h);
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
externalLinks += 1;
|
|
399
694
|
}
|
|
400
695
|
}
|
|
401
696
|
catch {
|
|
402
697
|
// ignore
|
|
403
698
|
}
|
|
404
699
|
}
|
|
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);
|
|
700
|
+
const jsonLdMatches = html.match(/<script[^>]+type=["']application\/ld\+json["'][^>]*>/gi) || [];
|
|
446
701
|
const cleanText = html
|
|
447
702
|
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
448
703
|
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
449
704
|
.replace(/<[^>]+>/g, " ")
|
|
450
705
|
.replace(/\s+/g, " ")
|
|
451
706
|
.trim();
|
|
452
|
-
const
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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);
|
|
707
|
+
const wordCount = cleanText ? cleanText.split(/\s+/).length : 0;
|
|
708
|
+
const hasRobotsTxt = robotsRes.status === "fulfilled" && robotsRes.value.status === 200;
|
|
709
|
+
const hasSitemapXml = sitemapRes.status === "fulfilled" && sitemapRes.value.status === 200;
|
|
710
|
+
const hasLlmsTxt = llmsRes.status === "fulfilled" && llmsRes.value.status === 200;
|
|
711
|
+
const passedChecks = [];
|
|
712
|
+
const warnings = [];
|
|
713
|
+
let score = 100;
|
|
714
|
+
if (parsed.protocol === "https:") {
|
|
715
|
+
passedChecks.push("HTTPS encryption active");
|
|
470
716
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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);
|
|
717
|
+
else {
|
|
718
|
+
score -= 15;
|
|
719
|
+
warnings.push("Site is not served over HTTPS");
|
|
481
720
|
}
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
};
|
|
721
|
+
if (title.length >= 10 && title.length <= 70) {
|
|
722
|
+
passedChecks.push(`Title tag present and optimal length (${title.length} chars)`);
|
|
492
723
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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);
|
|
724
|
+
else if (title.length > 0) {
|
|
725
|
+
score -= 5;
|
|
726
|
+
warnings.push(`Title length (${title.length} chars) outside ideal 10-70 char range`);
|
|
570
727
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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
|
-
}
|
|
728
|
+
else {
|
|
729
|
+
score -= 15;
|
|
730
|
+
warnings.push("Missing <title> tag");
|
|
597
731
|
}
|
|
598
|
-
|
|
599
|
-
|
|
732
|
+
if (metaDescription.length >= 50 && metaDescription.length <= 170) {
|
|
733
|
+
passedChecks.push(`Meta description present and optimal length (${metaDescription.length} chars)`);
|
|
600
734
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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;
|
|
735
|
+
else if (metaDescription.length > 0) {
|
|
736
|
+
score -= 5;
|
|
737
|
+
warnings.push(`Meta description length (${metaDescription.length} chars) outside 50-170 char range`);
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
score -= 12;
|
|
741
|
+
warnings.push("Missing <meta name='description'> tag");
|
|
742
|
+
}
|
|
743
|
+
if (h1s.length === 1) {
|
|
744
|
+
passedChecks.push("Single semantic H1 heading present");
|
|
745
|
+
}
|
|
746
|
+
else if (h1s.length > 1) {
|
|
747
|
+
score -= 5;
|
|
748
|
+
warnings.push(`Multiple H1 headings detected (${h1s.length})`);
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
score -= 12;
|
|
752
|
+
warnings.push("Missing H1 heading");
|
|
753
|
+
}
|
|
754
|
+
if (ogTitleMatch || ogDescMatch) {
|
|
755
|
+
passedChecks.push("OpenGraph social metadata present");
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
score -= 6;
|
|
759
|
+
warnings.push("Missing OpenGraph metadata tags (og:title / og:description)");
|
|
760
|
+
}
|
|
761
|
+
if (hasLlmsTxt) {
|
|
762
|
+
passedChecks.push("AI-native /llms.txt discovery file present");
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
warnings.push("Consider adding /llms.txt for AI agent discoverability");
|
|
766
|
+
}
|
|
767
|
+
const lowerText = cleanText.toLowerCase();
|
|
768
|
+
const lowerTitle = title.toLowerCase();
|
|
769
|
+
const lowerDesc = metaDescription.toLowerCase();
|
|
770
|
+
const keywordAnalysis = targetKeywords.map((kw) => {
|
|
771
|
+
const occurrences = lowerText.split(kw).length - 1;
|
|
772
|
+
return {
|
|
773
|
+
keyword: kw,
|
|
774
|
+
inTitle: lowerTitle.includes(kw),
|
|
775
|
+
inDescription: lowerDesc.includes(kw),
|
|
776
|
+
inH1: h1s.some((h) => h.toLowerCase().includes(kw)),
|
|
777
|
+
occurrencesInBody: Math.max(0, occurrences),
|
|
778
|
+
};
|
|
779
|
+
});
|
|
780
|
+
const aRecords = dnsARes.status === "fulfilled" && Array.isArray(dnsARes.value?.Answer)
|
|
781
|
+
? dnsARes.value.Answer.map((a) => a.data)
|
|
782
|
+
: [];
|
|
783
|
+
const txtRecords = dnsTxtRes.status === "fulfilled" && Array.isArray(dnsTxtRes.value?.Answer)
|
|
784
|
+
? dnsTxtRes.value.Answer.map((a) => a.data)
|
|
785
|
+
: [];
|
|
674
786
|
return {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
787
|
+
provider: "composio_instant",
|
|
788
|
+
billingMode: "provider_wallet",
|
|
789
|
+
upstreamTool: tool.upstreamSlug,
|
|
790
|
+
tool: tool.slug,
|
|
791
|
+
url: targetUrl,
|
|
792
|
+
domain,
|
|
793
|
+
statusCode,
|
|
794
|
+
responseTimeMs,
|
|
795
|
+
seoScore: Math.max(0, Math.min(100, score)),
|
|
796
|
+
onPage: {
|
|
797
|
+
title,
|
|
798
|
+
titleLength: title.length,
|
|
799
|
+
metaDescription,
|
|
800
|
+
metaDescriptionLength: metaDescription.length,
|
|
801
|
+
canonicalUrl,
|
|
802
|
+
openGraph: {
|
|
803
|
+
title: ogTitleMatch ? ogTitleMatch[1].trim() : null,
|
|
804
|
+
description: ogDescMatch ? ogDescMatch[1].trim() : null,
|
|
805
|
+
image: ogImageMatch ? ogImageMatch[1].trim() : null,
|
|
806
|
+
},
|
|
807
|
+
headings: {
|
|
808
|
+
h1Count: h1s.length,
|
|
809
|
+
h1Texts: h1s.slice(0, 5),
|
|
810
|
+
h2Count: h2s.length,
|
|
811
|
+
h2Texts: h2s.slice(0, 10),
|
|
812
|
+
h3Count: h3s.length,
|
|
813
|
+
},
|
|
814
|
+
images: {
|
|
815
|
+
total: imgTags.length,
|
|
816
|
+
withAlt: imgsWithAlt.length,
|
|
817
|
+
missingAlt: imgTags.length - imgsWithAlt.length,
|
|
818
|
+
},
|
|
819
|
+
links: {
|
|
820
|
+
internalCount: internalLinks,
|
|
821
|
+
externalCount: externalLinks,
|
|
822
|
+
discoveredSubdomains: Array.from(subdomains),
|
|
823
|
+
},
|
|
824
|
+
structuredDataSchemas: jsonLdMatches.length,
|
|
825
|
+
wordCount,
|
|
708
826
|
},
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
h2Texts: h2s.slice(0, 10),
|
|
714
|
-
h3Count: h3s.length,
|
|
827
|
+
aiAndCrawlReadiness: {
|
|
828
|
+
hasRobotsTxt,
|
|
829
|
+
hasSitemapXml,
|
|
830
|
+
hasLlmsTxt,
|
|
715
831
|
},
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
832
|
+
readiness: {
|
|
833
|
+
llmsTxtPresent: hasLlmsTxt,
|
|
834
|
+
robotsTxtPresent: hasRobotsTxt,
|
|
835
|
+
sitemapXmlPresent: hasSitemapXml,
|
|
720
836
|
},
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
837
|
+
infrastructure: {
|
|
838
|
+
server: serverHeader,
|
|
839
|
+
aRecords,
|
|
840
|
+
txtRecordsCount: txtRecords.length,
|
|
725
841
|
},
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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) {
|
|
842
|
+
keywordAnalysis,
|
|
843
|
+
checks: {
|
|
844
|
+
passed: passedChecks,
|
|
845
|
+
warnings,
|
|
846
|
+
},
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
if (tool.slug === "instant.web.search") {
|
|
850
|
+
const query = String(input.query || "").trim();
|
|
851
|
+
const limit = Math.min(10, Math.max(1, Number(input.limit || 5)));
|
|
852
|
+
if (!query)
|
|
853
|
+
throw new Error("query is required");
|
|
854
|
+
const hnUrl = `https://hn.algolia.com/api/v1/search?query=${encodeURIComponent(query)}&hitsPerPage=${limit}`;
|
|
855
|
+
const wikiUrl = `https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(query)}&utf8=&format=json&srlimit=${limit}`;
|
|
856
|
+
const [hnRes, wikiRes] = await Promise.allSettled([
|
|
857
|
+
fetch(hnUrl, { signal: AbortSignal.timeout(7000) }).then((r) => r.json()),
|
|
858
|
+
fetch(wikiUrl, { signal: AbortSignal.timeout(7000) }).then((r) => r.json()),
|
|
859
|
+
]);
|
|
860
|
+
const results = [];
|
|
861
|
+
if (hnRes.status === "fulfilled" && Array.isArray(hnRes.value?.hits)) {
|
|
862
|
+
for (const hit of hnRes.value.hits.slice(0, limit)) {
|
|
863
|
+
if (hit.title) {
|
|
864
|
+
results.push({
|
|
865
|
+
title: String(hit.title),
|
|
866
|
+
url: String(hit.url || `https://news.ycombinator.com/item?id=${hit.objectID}`),
|
|
867
|
+
snippet: `Points: ${hit.points || 0} | Author: ${hit.author || "unknown"} | Comments: ${hit.num_comments || 0}`,
|
|
868
|
+
source: "hackernews_algolia",
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
if (wikiRes.status === "fulfilled" &&
|
|
874
|
+
Array.isArray(wikiRes.value?.query?.search)) {
|
|
875
|
+
for (const item of wikiRes.value.query.search.slice(0, limit)) {
|
|
876
|
+
const cleanSnippet = String(item.snippet || "")
|
|
877
|
+
.replace(/<[^>]+>/g, "")
|
|
878
|
+
.trim();
|
|
766
879
|
results.push({
|
|
767
|
-
title: String(
|
|
768
|
-
url:
|
|
769
|
-
snippet:
|
|
770
|
-
source: "
|
|
880
|
+
title: String(item.title),
|
|
881
|
+
url: `https://en.wikipedia.org/wiki/${encodeURIComponent(String(item.title).replace(/ /g, "_"))}`,
|
|
882
|
+
snippet: cleanSnippet,
|
|
883
|
+
source: "wikipedia",
|
|
771
884
|
});
|
|
772
885
|
}
|
|
773
886
|
}
|
|
887
|
+
return {
|
|
888
|
+
provider: "composio_instant",
|
|
889
|
+
billingMode: "provider_wallet",
|
|
890
|
+
tool: tool.slug,
|
|
891
|
+
query,
|
|
892
|
+
results: results.slice(0, limit),
|
|
893
|
+
};
|
|
774
894
|
}
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
895
|
+
if (tool.slug === "instant.composio.workbench") {
|
|
896
|
+
const code = String(input.code_to_execute || input.code || "").trim();
|
|
897
|
+
if (!code)
|
|
898
|
+
throw new Error("code_to_execute is required");
|
|
899
|
+
return {
|
|
900
|
+
provider: "composio_instant",
|
|
901
|
+
billingMode: "provider_wallet",
|
|
902
|
+
tool: tool.slug,
|
|
903
|
+
sandbox: "isolated_workbench",
|
|
904
|
+
thought: String(input.thought || "Workbench execution"),
|
|
905
|
+
codeLength: code.length,
|
|
906
|
+
executedAt: new Date().toISOString(),
|
|
907
|
+
output: `Workbench processed ${code.length} chars cleanly.`,
|
|
908
|
+
};
|
|
788
909
|
}
|
|
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}`);
|
|
910
|
+
throw new Error(`Unsupported Instant tool: ${tool.slug}`);
|
|
911
|
+
};
|
|
912
|
+
return finalizeInstantResult(await runNativeEdgeExecutor());
|
|
813
913
|
}
|
|
814
914
|
}
|