New:Socket for Asana Is Now Available.Learn more
Get Started

aigentify

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aigentify - npm Package Compare versions

Comparing version
0.3.0
to
0.4.0
+152
-7
dist/cli.cjs

@@ -39,3 +39,3 @@ #!/usr/bin/env node

name: "aigentify",
version: "0.3.0",
version: "0.4.0",
mcpName: "io.github.pooriaarab/aigentify",

@@ -218,2 +218,4 @@ description: "Audit and generate agent-native product surfaces",

agentCard: { status: 404, contentType: "", text: "" },
aiPlugin: { status: 404, contentType: "", text: "" },
external: NO_EXTERNAL_DISCOVERY,
honestText,

@@ -237,2 +239,105 @@ hasMcp: Boolean(mcpFile || serverFile || hasMcpBin),

}
function productName(agentCard, home) {
const card = parseJson(agentCard.text);
if (card && typeof card.name === "string" && card.name.trim()) return card.name.trim();
const title = /<title[^>]*>([^<]{2,120})<\/title>/i.exec(home.text)?.[1]?.trim();
return title ? title.split(/\s[|–—-]\s/)[0].trim() : null;
}
async function fetchJson(fetcher, url) {
try {
const res = await fetcher(url, {
signal: AbortSignal.timeout(8e3),
headers: { accept: "application/json", "user-agent": EXTERNAL_UA }
});
if (!ok(res.status)) return REGISTRY_ERROR;
return await res.json();
} catch {
return REGISTRY_ERROR;
}
}
function normalize(s) {
return s.toLowerCase().replace(/[^a-z0-9]+/g, "");
}
function safeHost(url) {
try {
return new URL(url).host.replace(/^www\./i, "").toLowerCase();
} catch {
return "";
}
}
async function probeExternalDiscovery(fetcher, base, name) {
const host = (() => {
try {
return new URL(base).host.replace(/^www\./, "");
} catch {
return base;
}
})();
const productNorm = name ? normalize(name) : "";
const hostLabelNorm = normalize(host.split(".")[0]);
const wantNorms = [productNorm, hostLabelNorm].filter(Boolean);
const wikidataVariants = ["https://", "http://"].flatMap(
(scheme) => [host, `www.${host}`].flatMap((h) => ["", "/"].map((slash) => `${scheme}${h}${slash}`))
);
const wikidataP = Promise.all(
wikidataVariants.map(
(val) => fetchJson(
fetcher,
`https://www.wikidata.org/w/api.php?action=query&list=search&format=json&srsearch=${encodeURIComponent(
`haswbstatement:P856=${val}`
)}`
)
)
).then((results) => {
const found = results.some(
(d) => d !== REGISTRY_ERROR && (d?.query?.searchinfo?.totalhits ?? 0) > 0
);
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const npmQuery = name ?? host.split(".")[0];
const npmP = fetchJson(fetcher, `https://registry.npmjs.org/-/v1/search?size=20&text=${encodeURIComponent(npmQuery)}`).then((d) => {
if (d === REGISTRY_ERROR) return { found: false, errored: true };
const rawObjects = d?.objects;
const objects = Array.isArray(rawObjects) ? rawObjects : [];
const found = objects.some((o) => {
const pkgName = (o.package?.name ?? "").toLowerCase();
if (!pkgName) return false;
const scope = /^@([^/]+)\//.exec(pkgName)?.[1] ?? "";
const unscoped = pkgName.replace(/^@[^/]+\//, "");
const candidates = [pkgName, unscoped, scope].filter(Boolean).map(normalize);
const nameMatches = candidates.some((c) => wantNorms.includes(c));
const homepageHost = safeHost(o.package?.links?.homepage ?? "");
return nameMatches || homepageHost !== "" && homepageHost === host;
});
return { found, errored: false };
});
const slug = (name ?? host.split(".")[0]).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
const mcpP = Promise.all(
[slug, host].map((q) => fetchJson(fetcher, `https://registry.modelcontextprotocol.io/v0/servers?search=${encodeURIComponent(q)}`))
).then((results) => {
const found = results.some((d) => {
if (d === REGISTRY_ERROR) return false;
const rawServers = d?.servers;
const servers = Array.isArray(rawServers) ? rawServers : [];
return servers.some((entry) => {
const server = entry?.server ?? entry;
const rawName = String(server?.name ?? "");
const shortName = rawName.includes("/") ? rawName.slice(rawName.lastIndexOf("/") + 1) : rawName;
const websiteHost = safeHost(String(server?.websiteUrl ?? ""));
const repoHost = safeHost(String(server?.repository?.url ?? ""));
return wantNorms.includes(normalize(shortName)) || websiteHost === host || repoHost === host;
});
});
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const [wikidata, npm, mcpRegistry] = await Promise.all([wikidataP, npmP, mcpP]);
return {
wikidata: wikidata.found,
wikidataError: wikidata.errored,
npm: npm.found,
npmError: npm.errored,
mcpRegistry: mcpRegistry.found,
mcpRegistryError: mcpRegistry.errored
};
}
async function urlSnapshot(target, fetcher) {

@@ -256,3 +361,4 @@ const base = target.replace(/\/+$/, "");

authMd,
apiCatalog
apiCatalog,
aiPlugin
] = await Promise.all([

@@ -273,3 +379,4 @@ fetchText(fetcher, `${base}/agents.md`),

fetchText(fetcher, `${base}/auth.md`),
fetchText(fetcher, `${base}/.well-known/api-catalog`)
fetchText(fetcher, `${base}/.well-known/api-catalog`),
fetchText(fetcher, `${base}/.well-known/ai-plugin.json`)
]);

@@ -279,2 +386,3 @@ const agentCard = wellKnownCard;

const wk = ok(wellKnown.status) ? wellKnown : wellKnownCard;
const external = endpoints.every((item) => item.status === 0) ? NO_EXTERNAL_DISCOVERY : await probeExternalDiscovery(fetcher, base, productName(agentCard, home));
return {

@@ -296,2 +404,4 @@ agents,

agentCard,
aiPlugin,
external,
honestText: [home.text, offerBlock(agents.text)].join("\n"),

@@ -391,2 +501,14 @@ hasMcp: server.status >= 200 && server.status < 400 || mcp.status >= 200 && mcp.status < 400,

const markdownAltStatus = !urlOnly ? "na" : [...snapshot.home.text.matchAll(/<link\b[^>]*>/gi)].some(([tag]) => /rel=["']?alternate["']?/i.test(tag) && /type=["']?text\/markdown/i.test(tag)) ? "pass" : "warn";
const aiPluginManifest = (ok(snapshot.aiPlugin.status) ? parseJson(snapshot.aiPlugin.text) : void 0) ?? {};
const aiPluginStatus = !urlOnly ? "na" : (() => {
const isObject = (value) => typeof value === "object" && value !== null;
const hasName = ["name_for_model", "name_for_human"].some((key) => {
const value = aiPluginManifest[key];
return typeof value === "string" && value.trim().length > 0;
});
return hasName && isObject(aiPluginManifest.api) && isObject(aiPluginManifest.auth) ? "pass" : "warn";
})();
const wikidataStatus = !urlOnly ? "na" : snapshot.external.wikidata ? "pass" : "warn";
const npmStatus = !urlOnly ? "na" : snapshot.external.npm ? "pass" : "warn";
const mcpRegistryStatus = !urlOnly ? "na" : snapshot.external.mcpRegistry ? "pass" : "warn";
return [

@@ -414,3 +536,7 @@ check("agents-md", agentsStatus, agentsPresent ? "AGENTS.md is available." : snapshot.agents.status === 0 ? "The AGENTS.md request failed." : "AGENTS.md is missing."),

check("link-headers", linkHeadersStatus, linkHeadersStatus === "pass" ? "The homepage returns RFC 8288 Link headers." : linkHeadersStatus === "na" ? "Not audited (no served web surface)." : "No Link header was found on the homepage."),
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.')
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.'),
check("ai-plugin", aiPluginStatus, aiPluginStatus === "pass" ? "A /.well-known/ai-plugin.json manifest is available." : aiPluginStatus === "na" ? "Not audited (no served web surface)." : "No usable /.well-known/ai-plugin.json manifest was found (missing, empty, or lacking identifying fields)."),
check("wikidata", wikidataStatus, wikidataStatus === "pass" ? "A Wikidata item links to this domain (P856)." : wikidataStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.wikidataError ? "The Wikidata lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No Wikidata item links to this domain via official website (P856)."),
check("npm-package", npmStatus, npmStatus === "pass" ? "A matching npm package is published." : npmStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.npmError ? "The npm registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No npm package matching the product was found."),
check("mcp-registry", mcpRegistryStatus, mcpRegistryStatus === "pass" ? "Listed in the official MCP registry." : mcpRegistryStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.mcpRegistryError ? "The MCP registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No entry matching this product was found in the official MCP registry.")
];

@@ -433,3 +559,3 @@ }

}
var import_promises, import_node_path, SKIPPED, WEIGHTS, FIXES;
var import_promises, import_node_path, NO_EXTERNAL_DISCOVERY, SKIPPED, WEIGHTS, FIXES, EXTERNAL_UA, REGISTRY_ERROR;
var init_audit = __esm({

@@ -441,2 +567,10 @@ "src/audit.ts"() {

init_constants();
NO_EXTERNAL_DISCOVERY = {
wikidata: false,
wikidataError: false,
npm: false,
npmError: false,
mcpRegistry: false,
mcpRegistryError: false
};
SKIPPED = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "coverage", ".next", "_reference_geoaeo"]);

@@ -468,3 +602,8 @@ WEIGHTS = {

"link-headers": 5,
"markdown-alt": 5
"markdown-alt": 5,
// External-discovery round 3 (queries third-party registries) — na for CLI/dir targets
"ai-plugin": 5,
wikidata: 5,
"npm-package": 5,
"mcp-registry": 5
};

@@ -493,4 +632,10 @@ FIXES = {

"link-headers": "Return RFC 8288 Link headers on the homepage pointing at llms.txt, openapi.json, and the agent card so agents discover descriptors without parsing HTML.",
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.'
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.',
"ai-plugin": "Publish /.well-known/ai-plugin.json (the plugin manifest) so plugin hosts and agents can auto-discover your API and auth.",
wikidata: "Create a Wikidata item for the product with an official-website (P856) statement pointing at your domain, so agents can verify the entity.",
"npm-package": "Publish an official SDK/CLI to npm under a discoverable name so agents can install a typed client.",
"mcp-registry": "List your MCP server in the official MCP registry (server.json + mcp-publisher) so agents discover it by name."
};
EXTERNAL_UA = "aigentify/0.4 (+https://github.com/pooriaarab/aigentify)";
REGISTRY_ERROR = Symbol("registry-error");
}

@@ -497,0 +642,0 @@ });

@@ -18,3 +18,3 @@ #!/usr/bin/env node

name: "aigentify",
version: "0.3.0",
version: "0.4.0",
mcpName: "io.github.pooriaarab/aigentify",

@@ -199,2 +199,4 @@ description: "Audit and generate agent-native product surfaces",

agentCard: { status: 404, contentType: "", text: "" },
aiPlugin: { status: 404, contentType: "", text: "" },
external: NO_EXTERNAL_DISCOVERY,
honestText,

@@ -218,2 +220,105 @@ hasMcp: Boolean(mcpFile || serverFile || hasMcpBin),

}
function productName(agentCard, home) {
const card = parseJson(agentCard.text);
if (card && typeof card.name === "string" && card.name.trim()) return card.name.trim();
const title = /<title[^>]*>([^<]{2,120})<\/title>/i.exec(home.text)?.[1]?.trim();
return title ? title.split(/\s[|–—-]\s/)[0].trim() : null;
}
async function fetchJson(fetcher, url) {
try {
const res = await fetcher(url, {
signal: AbortSignal.timeout(8e3),
headers: { accept: "application/json", "user-agent": EXTERNAL_UA }
});
if (!ok(res.status)) return REGISTRY_ERROR;
return await res.json();
} catch {
return REGISTRY_ERROR;
}
}
function normalize(s) {
return s.toLowerCase().replace(/[^a-z0-9]+/g, "");
}
function safeHost(url) {
try {
return new URL(url).host.replace(/^www\./i, "").toLowerCase();
} catch {
return "";
}
}
async function probeExternalDiscovery(fetcher, base, name) {
const host = (() => {
try {
return new URL(base).host.replace(/^www\./, "");
} catch {
return base;
}
})();
const productNorm = name ? normalize(name) : "";
const hostLabelNorm = normalize(host.split(".")[0]);
const wantNorms = [productNorm, hostLabelNorm].filter(Boolean);
const wikidataVariants = ["https://", "http://"].flatMap(
(scheme) => [host, `www.${host}`].flatMap((h) => ["", "/"].map((slash) => `${scheme}${h}${slash}`))
);
const wikidataP = Promise.all(
wikidataVariants.map(
(val) => fetchJson(
fetcher,
`https://www.wikidata.org/w/api.php?action=query&list=search&format=json&srsearch=${encodeURIComponent(
`haswbstatement:P856=${val}`
)}`
)
)
).then((results) => {
const found = results.some(
(d) => d !== REGISTRY_ERROR && (d?.query?.searchinfo?.totalhits ?? 0) > 0
);
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const npmQuery = name ?? host.split(".")[0];
const npmP = fetchJson(fetcher, `https://registry.npmjs.org/-/v1/search?size=20&text=${encodeURIComponent(npmQuery)}`).then((d) => {
if (d === REGISTRY_ERROR) return { found: false, errored: true };
const rawObjects = d?.objects;
const objects = Array.isArray(rawObjects) ? rawObjects : [];
const found = objects.some((o) => {
const pkgName = (o.package?.name ?? "").toLowerCase();
if (!pkgName) return false;
const scope = /^@([^/]+)\//.exec(pkgName)?.[1] ?? "";
const unscoped = pkgName.replace(/^@[^/]+\//, "");
const candidates = [pkgName, unscoped, scope].filter(Boolean).map(normalize);
const nameMatches = candidates.some((c) => wantNorms.includes(c));
const homepageHost = safeHost(o.package?.links?.homepage ?? "");
return nameMatches || homepageHost !== "" && homepageHost === host;
});
return { found, errored: false };
});
const slug = (name ?? host.split(".")[0]).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
const mcpP = Promise.all(
[slug, host].map((q) => fetchJson(fetcher, `https://registry.modelcontextprotocol.io/v0/servers?search=${encodeURIComponent(q)}`))
).then((results) => {
const found = results.some((d) => {
if (d === REGISTRY_ERROR) return false;
const rawServers = d?.servers;
const servers = Array.isArray(rawServers) ? rawServers : [];
return servers.some((entry) => {
const server = entry?.server ?? entry;
const rawName = String(server?.name ?? "");
const shortName = rawName.includes("/") ? rawName.slice(rawName.lastIndexOf("/") + 1) : rawName;
const websiteHost = safeHost(String(server?.websiteUrl ?? ""));
const repoHost = safeHost(String(server?.repository?.url ?? ""));
return wantNorms.includes(normalize(shortName)) || websiteHost === host || repoHost === host;
});
});
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const [wikidata, npm, mcpRegistry] = await Promise.all([wikidataP, npmP, mcpP]);
return {
wikidata: wikidata.found,
wikidataError: wikidata.errored,
npm: npm.found,
npmError: npm.errored,
mcpRegistry: mcpRegistry.found,
mcpRegistryError: mcpRegistry.errored
};
}
async function urlSnapshot(target, fetcher) {

@@ -237,3 +342,4 @@ const base = target.replace(/\/+$/, "");

authMd,
apiCatalog
apiCatalog,
aiPlugin
] = await Promise.all([

@@ -254,3 +360,4 @@ fetchText(fetcher, `${base}/agents.md`),

fetchText(fetcher, `${base}/auth.md`),
fetchText(fetcher, `${base}/.well-known/api-catalog`)
fetchText(fetcher, `${base}/.well-known/api-catalog`),
fetchText(fetcher, `${base}/.well-known/ai-plugin.json`)
]);

@@ -260,2 +367,3 @@ const agentCard = wellKnownCard;

const wk = ok(wellKnown.status) ? wellKnown : wellKnownCard;
const external = endpoints.every((item) => item.status === 0) ? NO_EXTERNAL_DISCOVERY : await probeExternalDiscovery(fetcher, base, productName(agentCard, home));
return {

@@ -277,2 +385,4 @@ agents,

agentCard,
aiPlugin,
external,
honestText: [home.text, offerBlock(agents.text)].join("\n"),

@@ -372,2 +482,14 @@ hasMcp: server.status >= 200 && server.status < 400 || mcp.status >= 200 && mcp.status < 400,

const markdownAltStatus = !urlOnly ? "na" : [...snapshot.home.text.matchAll(/<link\b[^>]*>/gi)].some(([tag]) => /rel=["']?alternate["']?/i.test(tag) && /type=["']?text\/markdown/i.test(tag)) ? "pass" : "warn";
const aiPluginManifest = (ok(snapshot.aiPlugin.status) ? parseJson(snapshot.aiPlugin.text) : void 0) ?? {};
const aiPluginStatus = !urlOnly ? "na" : (() => {
const isObject = (value) => typeof value === "object" && value !== null;
const hasName = ["name_for_model", "name_for_human"].some((key) => {
const value = aiPluginManifest[key];
return typeof value === "string" && value.trim().length > 0;
});
return hasName && isObject(aiPluginManifest.api) && isObject(aiPluginManifest.auth) ? "pass" : "warn";
})();
const wikidataStatus = !urlOnly ? "na" : snapshot.external.wikidata ? "pass" : "warn";
const npmStatus = !urlOnly ? "na" : snapshot.external.npm ? "pass" : "warn";
const mcpRegistryStatus = !urlOnly ? "na" : snapshot.external.mcpRegistry ? "pass" : "warn";
return [

@@ -395,3 +517,7 @@ check("agents-md", agentsStatus, agentsPresent ? "AGENTS.md is available." : snapshot.agents.status === 0 ? "The AGENTS.md request failed." : "AGENTS.md is missing."),

check("link-headers", linkHeadersStatus, linkHeadersStatus === "pass" ? "The homepage returns RFC 8288 Link headers." : linkHeadersStatus === "na" ? "Not audited (no served web surface)." : "No Link header was found on the homepage."),
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.')
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.'),
check("ai-plugin", aiPluginStatus, aiPluginStatus === "pass" ? "A /.well-known/ai-plugin.json manifest is available." : aiPluginStatus === "na" ? "Not audited (no served web surface)." : "No usable /.well-known/ai-plugin.json manifest was found (missing, empty, or lacking identifying fields)."),
check("wikidata", wikidataStatus, wikidataStatus === "pass" ? "A Wikidata item links to this domain (P856)." : wikidataStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.wikidataError ? "The Wikidata lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No Wikidata item links to this domain via official website (P856)."),
check("npm-package", npmStatus, npmStatus === "pass" ? "A matching npm package is published." : npmStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.npmError ? "The npm registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No npm package matching the product was found."),
check("mcp-registry", mcpRegistryStatus, mcpRegistryStatus === "pass" ? "Listed in the official MCP registry." : mcpRegistryStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.mcpRegistryError ? "The MCP registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No entry matching this product was found in the official MCP registry.")
];

@@ -414,3 +540,3 @@ }

}
var SKIPPED, WEIGHTS, FIXES;
var NO_EXTERNAL_DISCOVERY, SKIPPED, WEIGHTS, FIXES, EXTERNAL_UA, REGISTRY_ERROR;
var init_audit = __esm({

@@ -420,2 +546,10 @@ "src/audit.ts"() {

init_constants();
NO_EXTERNAL_DISCOVERY = {
wikidata: false,
wikidataError: false,
npm: false,
npmError: false,
mcpRegistry: false,
mcpRegistryError: false
};
SKIPPED = /* @__PURE__ */ new Set([".git", "node_modules", "dist", "coverage", ".next", "_reference_geoaeo"]);

@@ -447,3 +581,8 @@ WEIGHTS = {

"link-headers": 5,
"markdown-alt": 5
"markdown-alt": 5,
// External-discovery round 3 (queries third-party registries) — na for CLI/dir targets
"ai-plugin": 5,
wikidata: 5,
"npm-package": 5,
"mcp-registry": 5
};

@@ -472,4 +611,10 @@ FIXES = {

"link-headers": "Return RFC 8288 Link headers on the homepage pointing at llms.txt, openapi.json, and the agent card so agents discover descriptors without parsing HTML.",
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.'
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.',
"ai-plugin": "Publish /.well-known/ai-plugin.json (the plugin manifest) so plugin hosts and agents can auto-discover your API and auth.",
wikidata: "Create a Wikidata item for the product with an official-website (P856) statement pointing at your domain, so agents can verify the entity.",
"npm-package": "Publish an official SDK/CLI to npm under a discoverable name so agents can install a typed client.",
"mcp-registry": "List your MCP server in the official MCP registry (server.json + mcp-publisher) so agents discover it by name."
};
EXTERNAL_UA = "aigentify/0.4 (+https://github.com/pooriaarab/aigentify)";
REGISTRY_ERROR = Symbol("registry-error");
}

@@ -476,0 +621,0 @@ });

@@ -58,3 +58,3 @@ "use strict";

name: "aigentify",
version: "0.3.0",
version: "0.4.0",
mcpName: "io.github.pooriaarab/aigentify",

@@ -128,2 +128,10 @@ description: "Audit and generate agent-native product surfaces",

// src/audit.ts
var NO_EXTERNAL_DISCOVERY = {
wikidata: false,
wikidataError: false,
npm: false,
npmError: false,
mcpRegistry: false,
mcpRegistryError: false
};
function ok(status) {

@@ -158,3 +166,8 @@ return status >= 200 && status < 400;

"link-headers": 5,
"markdown-alt": 5
"markdown-alt": 5,
// External-discovery round 3 (queries third-party registries) — na for CLI/dir targets
"ai-plugin": 5,
wikidata: 5,
"npm-package": 5,
"mcp-registry": 5
};

@@ -183,3 +196,7 @@ var FIXES = {

"link-headers": "Return RFC 8288 Link headers on the homepage pointing at llms.txt, openapi.json, and the agent card so agents discover descriptors without parsing HTML.",
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.'
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.',
"ai-plugin": "Publish /.well-known/ai-plugin.json (the plugin manifest) so plugin hosts and agents can auto-discover your API and auth.",
wikidata: "Create a Wikidata item for the product with an official-website (P856) statement pointing at your domain, so agents can verify the entity.",
"npm-package": "Publish an official SDK/CLI to npm under a discoverable name so agents can install a typed client.",
"mcp-registry": "List your MCP server in the official MCP registry (server.json + mcp-publisher) so agents discover it by name."
};

@@ -283,2 +300,4 @@ function check(id, status, note) {

agentCard: { status: 404, contentType: "", text: "" },
aiPlugin: { status: 404, contentType: "", text: "" },
external: NO_EXTERNAL_DISCOVERY,
honestText,

@@ -302,2 +321,107 @@ hasMcp: Boolean(mcpFile || serverFile || hasMcpBin),

}
function productName(agentCard, home) {
const card = parseJson(agentCard.text);
if (card && typeof card.name === "string" && card.name.trim()) return card.name.trim();
const title = /<title[^>]*>([^<]{2,120})<\/title>/i.exec(home.text)?.[1]?.trim();
return title ? title.split(/\s[|–—-]\s/)[0].trim() : null;
}
var EXTERNAL_UA = "aigentify/0.4 (+https://github.com/pooriaarab/aigentify)";
var REGISTRY_ERROR = Symbol("registry-error");
async function fetchJson(fetcher, url) {
try {
const res = await fetcher(url, {
signal: AbortSignal.timeout(8e3),
headers: { accept: "application/json", "user-agent": EXTERNAL_UA }
});
if (!ok(res.status)) return REGISTRY_ERROR;
return await res.json();
} catch {
return REGISTRY_ERROR;
}
}
function normalize(s) {
return s.toLowerCase().replace(/[^a-z0-9]+/g, "");
}
function safeHost(url) {
try {
return new URL(url).host.replace(/^www\./i, "").toLowerCase();
} catch {
return "";
}
}
async function probeExternalDiscovery(fetcher, base, name) {
const host = (() => {
try {
return new URL(base).host.replace(/^www\./, "");
} catch {
return base;
}
})();
const productNorm = name ? normalize(name) : "";
const hostLabelNorm = normalize(host.split(".")[0]);
const wantNorms = [productNorm, hostLabelNorm].filter(Boolean);
const wikidataVariants = ["https://", "http://"].flatMap(
(scheme) => [host, `www.${host}`].flatMap((h) => ["", "/"].map((slash) => `${scheme}${h}${slash}`))
);
const wikidataP = Promise.all(
wikidataVariants.map(
(val) => fetchJson(
fetcher,
`https://www.wikidata.org/w/api.php?action=query&list=search&format=json&srsearch=${encodeURIComponent(
`haswbstatement:P856=${val}`
)}`
)
)
).then((results) => {
const found = results.some(
(d) => d !== REGISTRY_ERROR && (d?.query?.searchinfo?.totalhits ?? 0) > 0
);
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const npmQuery = name ?? host.split(".")[0];
const npmP = fetchJson(fetcher, `https://registry.npmjs.org/-/v1/search?size=20&text=${encodeURIComponent(npmQuery)}`).then((d) => {
if (d === REGISTRY_ERROR) return { found: false, errored: true };
const rawObjects = d?.objects;
const objects = Array.isArray(rawObjects) ? rawObjects : [];
const found = objects.some((o) => {
const pkgName = (o.package?.name ?? "").toLowerCase();
if (!pkgName) return false;
const scope = /^@([^/]+)\//.exec(pkgName)?.[1] ?? "";
const unscoped = pkgName.replace(/^@[^/]+\//, "");
const candidates = [pkgName, unscoped, scope].filter(Boolean).map(normalize);
const nameMatches = candidates.some((c) => wantNorms.includes(c));
const homepageHost = safeHost(o.package?.links?.homepage ?? "");
return nameMatches || homepageHost !== "" && homepageHost === host;
});
return { found, errored: false };
});
const slug = (name ?? host.split(".")[0]).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
const mcpP = Promise.all(
[slug, host].map((q) => fetchJson(fetcher, `https://registry.modelcontextprotocol.io/v0/servers?search=${encodeURIComponent(q)}`))
).then((results) => {
const found = results.some((d) => {
if (d === REGISTRY_ERROR) return false;
const rawServers = d?.servers;
const servers = Array.isArray(rawServers) ? rawServers : [];
return servers.some((entry) => {
const server = entry?.server ?? entry;
const rawName = String(server?.name ?? "");
const shortName = rawName.includes("/") ? rawName.slice(rawName.lastIndexOf("/") + 1) : rawName;
const websiteHost = safeHost(String(server?.websiteUrl ?? ""));
const repoHost = safeHost(String(server?.repository?.url ?? ""));
return wantNorms.includes(normalize(shortName)) || websiteHost === host || repoHost === host;
});
});
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const [wikidata, npm, mcpRegistry] = await Promise.all([wikidataP, npmP, mcpP]);
return {
wikidata: wikidata.found,
wikidataError: wikidata.errored,
npm: npm.found,
npmError: npm.errored,
mcpRegistry: mcpRegistry.found,
mcpRegistryError: mcpRegistry.errored
};
}
async function urlSnapshot(target, fetcher) {

@@ -321,3 +445,4 @@ const base = target.replace(/\/+$/, "");

authMd,
apiCatalog
apiCatalog,
aiPlugin
] = await Promise.all([

@@ -338,3 +463,4 @@ fetchText(fetcher, `${base}/agents.md`),

fetchText(fetcher, `${base}/auth.md`),
fetchText(fetcher, `${base}/.well-known/api-catalog`)
fetchText(fetcher, `${base}/.well-known/api-catalog`),
fetchText(fetcher, `${base}/.well-known/ai-plugin.json`)
]);

@@ -344,2 +470,3 @@ const agentCard = wellKnownCard;

const wk = ok(wellKnown.status) ? wellKnown : wellKnownCard;
const external = endpoints.every((item) => item.status === 0) ? NO_EXTERNAL_DISCOVERY : await probeExternalDiscovery(fetcher, base, productName(agentCard, home));
return {

@@ -361,2 +488,4 @@ agents,

agentCard,
aiPlugin,
external,
honestText: [home.text, offerBlock(agents.text)].join("\n"),

@@ -456,2 +585,14 @@ hasMcp: server.status >= 200 && server.status < 400 || mcp.status >= 200 && mcp.status < 400,

const markdownAltStatus = !urlOnly ? "na" : [...snapshot.home.text.matchAll(/<link\b[^>]*>/gi)].some(([tag]) => /rel=["']?alternate["']?/i.test(tag) && /type=["']?text\/markdown/i.test(tag)) ? "pass" : "warn";
const aiPluginManifest = (ok(snapshot.aiPlugin.status) ? parseJson(snapshot.aiPlugin.text) : void 0) ?? {};
const aiPluginStatus = !urlOnly ? "na" : (() => {
const isObject = (value) => typeof value === "object" && value !== null;
const hasName = ["name_for_model", "name_for_human"].some((key) => {
const value = aiPluginManifest[key];
return typeof value === "string" && value.trim().length > 0;
});
return hasName && isObject(aiPluginManifest.api) && isObject(aiPluginManifest.auth) ? "pass" : "warn";
})();
const wikidataStatus = !urlOnly ? "na" : snapshot.external.wikidata ? "pass" : "warn";
const npmStatus = !urlOnly ? "na" : snapshot.external.npm ? "pass" : "warn";
const mcpRegistryStatus = !urlOnly ? "na" : snapshot.external.mcpRegistry ? "pass" : "warn";
return [

@@ -479,3 +620,7 @@ check("agents-md", agentsStatus, agentsPresent ? "AGENTS.md is available." : snapshot.agents.status === 0 ? "The AGENTS.md request failed." : "AGENTS.md is missing."),

check("link-headers", linkHeadersStatus, linkHeadersStatus === "pass" ? "The homepage returns RFC 8288 Link headers." : linkHeadersStatus === "na" ? "Not audited (no served web surface)." : "No Link header was found on the homepage."),
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.')
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.'),
check("ai-plugin", aiPluginStatus, aiPluginStatus === "pass" ? "A /.well-known/ai-plugin.json manifest is available." : aiPluginStatus === "na" ? "Not audited (no served web surface)." : "No usable /.well-known/ai-plugin.json manifest was found (missing, empty, or lacking identifying fields)."),
check("wikidata", wikidataStatus, wikidataStatus === "pass" ? "A Wikidata item links to this domain (P856)." : wikidataStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.wikidataError ? "The Wikidata lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No Wikidata item links to this domain via official website (P856)."),
check("npm-package", npmStatus, npmStatus === "pass" ? "A matching npm package is published." : npmStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.npmError ? "The npm registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No npm package matching the product was found."),
check("mcp-registry", mcpRegistryStatus, mcpRegistryStatus === "pass" ? "Listed in the official MCP registry." : mcpRegistryStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.mcpRegistryError ? "The MCP registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No entry matching this product was found in the official MCP registry.")
];

@@ -482,0 +627,0 @@ }

@@ -8,3 +8,3 @@ // src/audit.ts

name: "aigentify",
version: "0.3.0",
version: "0.4.0",
mcpName: "io.github.pooriaarab/aigentify",

@@ -78,2 +78,10 @@ description: "Audit and generate agent-native product surfaces",

// src/audit.ts
var NO_EXTERNAL_DISCOVERY = {
wikidata: false,
wikidataError: false,
npm: false,
npmError: false,
mcpRegistry: false,
mcpRegistryError: false
};
function ok(status) {

@@ -108,3 +116,8 @@ return status >= 200 && status < 400;

"link-headers": 5,
"markdown-alt": 5
"markdown-alt": 5,
// External-discovery round 3 (queries third-party registries) — na for CLI/dir targets
"ai-plugin": 5,
wikidata: 5,
"npm-package": 5,
"mcp-registry": 5
};

@@ -133,3 +146,7 @@ var FIXES = {

"link-headers": "Return RFC 8288 Link headers on the homepage pointing at llms.txt, openapi.json, and the agent card so agents discover descriptors without parsing HTML.",
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.'
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.',
"ai-plugin": "Publish /.well-known/ai-plugin.json (the plugin manifest) so plugin hosts and agents can auto-discover your API and auth.",
wikidata: "Create a Wikidata item for the product with an official-website (P856) statement pointing at your domain, so agents can verify the entity.",
"npm-package": "Publish an official SDK/CLI to npm under a discoverable name so agents can install a typed client.",
"mcp-registry": "List your MCP server in the official MCP registry (server.json + mcp-publisher) so agents discover it by name."
};

@@ -233,2 +250,4 @@ function check(id, status, note) {

agentCard: { status: 404, contentType: "", text: "" },
aiPlugin: { status: 404, contentType: "", text: "" },
external: NO_EXTERNAL_DISCOVERY,
honestText,

@@ -252,2 +271,107 @@ hasMcp: Boolean(mcpFile || serverFile || hasMcpBin),

}
function productName(agentCard, home) {
const card = parseJson(agentCard.text);
if (card && typeof card.name === "string" && card.name.trim()) return card.name.trim();
const title = /<title[^>]*>([^<]{2,120})<\/title>/i.exec(home.text)?.[1]?.trim();
return title ? title.split(/\s[|–—-]\s/)[0].trim() : null;
}
var EXTERNAL_UA = "aigentify/0.4 (+https://github.com/pooriaarab/aigentify)";
var REGISTRY_ERROR = Symbol("registry-error");
async function fetchJson(fetcher, url) {
try {
const res = await fetcher(url, {
signal: AbortSignal.timeout(8e3),
headers: { accept: "application/json", "user-agent": EXTERNAL_UA }
});
if (!ok(res.status)) return REGISTRY_ERROR;
return await res.json();
} catch {
return REGISTRY_ERROR;
}
}
function normalize(s) {
return s.toLowerCase().replace(/[^a-z0-9]+/g, "");
}
function safeHost(url) {
try {
return new URL(url).host.replace(/^www\./i, "").toLowerCase();
} catch {
return "";
}
}
async function probeExternalDiscovery(fetcher, base, name) {
const host = (() => {
try {
return new URL(base).host.replace(/^www\./, "");
} catch {
return base;
}
})();
const productNorm = name ? normalize(name) : "";
const hostLabelNorm = normalize(host.split(".")[0]);
const wantNorms = [productNorm, hostLabelNorm].filter(Boolean);
const wikidataVariants = ["https://", "http://"].flatMap(
(scheme) => [host, `www.${host}`].flatMap((h) => ["", "/"].map((slash) => `${scheme}${h}${slash}`))
);
const wikidataP = Promise.all(
wikidataVariants.map(
(val) => fetchJson(
fetcher,
`https://www.wikidata.org/w/api.php?action=query&list=search&format=json&srsearch=${encodeURIComponent(
`haswbstatement:P856=${val}`
)}`
)
)
).then((results) => {
const found = results.some(
(d) => d !== REGISTRY_ERROR && (d?.query?.searchinfo?.totalhits ?? 0) > 0
);
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const npmQuery = name ?? host.split(".")[0];
const npmP = fetchJson(fetcher, `https://registry.npmjs.org/-/v1/search?size=20&text=${encodeURIComponent(npmQuery)}`).then((d) => {
if (d === REGISTRY_ERROR) return { found: false, errored: true };
const rawObjects = d?.objects;
const objects = Array.isArray(rawObjects) ? rawObjects : [];
const found = objects.some((o) => {
const pkgName = (o.package?.name ?? "").toLowerCase();
if (!pkgName) return false;
const scope = /^@([^/]+)\//.exec(pkgName)?.[1] ?? "";
const unscoped = pkgName.replace(/^@[^/]+\//, "");
const candidates = [pkgName, unscoped, scope].filter(Boolean).map(normalize);
const nameMatches = candidates.some((c) => wantNorms.includes(c));
const homepageHost = safeHost(o.package?.links?.homepage ?? "");
return nameMatches || homepageHost !== "" && homepageHost === host;
});
return { found, errored: false };
});
const slug = (name ?? host.split(".")[0]).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
const mcpP = Promise.all(
[slug, host].map((q) => fetchJson(fetcher, `https://registry.modelcontextprotocol.io/v0/servers?search=${encodeURIComponent(q)}`))
).then((results) => {
const found = results.some((d) => {
if (d === REGISTRY_ERROR) return false;
const rawServers = d?.servers;
const servers = Array.isArray(rawServers) ? rawServers : [];
return servers.some((entry) => {
const server = entry?.server ?? entry;
const rawName = String(server?.name ?? "");
const shortName = rawName.includes("/") ? rawName.slice(rawName.lastIndexOf("/") + 1) : rawName;
const websiteHost = safeHost(String(server?.websiteUrl ?? ""));
const repoHost = safeHost(String(server?.repository?.url ?? ""));
return wantNorms.includes(normalize(shortName)) || websiteHost === host || repoHost === host;
});
});
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const [wikidata, npm, mcpRegistry] = await Promise.all([wikidataP, npmP, mcpP]);
return {
wikidata: wikidata.found,
wikidataError: wikidata.errored,
npm: npm.found,
npmError: npm.errored,
mcpRegistry: mcpRegistry.found,
mcpRegistryError: mcpRegistry.errored
};
}
async function urlSnapshot(target, fetcher) {

@@ -271,3 +395,4 @@ const base = target.replace(/\/+$/, "");

authMd,
apiCatalog
apiCatalog,
aiPlugin
] = await Promise.all([

@@ -288,3 +413,4 @@ fetchText(fetcher, `${base}/agents.md`),

fetchText(fetcher, `${base}/auth.md`),
fetchText(fetcher, `${base}/.well-known/api-catalog`)
fetchText(fetcher, `${base}/.well-known/api-catalog`),
fetchText(fetcher, `${base}/.well-known/ai-plugin.json`)
]);

@@ -294,2 +420,3 @@ const agentCard = wellKnownCard;

const wk = ok(wellKnown.status) ? wellKnown : wellKnownCard;
const external = endpoints.every((item) => item.status === 0) ? NO_EXTERNAL_DISCOVERY : await probeExternalDiscovery(fetcher, base, productName(agentCard, home));
return {

@@ -311,2 +438,4 @@ agents,

agentCard,
aiPlugin,
external,
honestText: [home.text, offerBlock(agents.text)].join("\n"),

@@ -406,2 +535,14 @@ hasMcp: server.status >= 200 && server.status < 400 || mcp.status >= 200 && mcp.status < 400,

const markdownAltStatus = !urlOnly ? "na" : [...snapshot.home.text.matchAll(/<link\b[^>]*>/gi)].some(([tag]) => /rel=["']?alternate["']?/i.test(tag) && /type=["']?text\/markdown/i.test(tag)) ? "pass" : "warn";
const aiPluginManifest = (ok(snapshot.aiPlugin.status) ? parseJson(snapshot.aiPlugin.text) : void 0) ?? {};
const aiPluginStatus = !urlOnly ? "na" : (() => {
const isObject = (value) => typeof value === "object" && value !== null;
const hasName = ["name_for_model", "name_for_human"].some((key) => {
const value = aiPluginManifest[key];
return typeof value === "string" && value.trim().length > 0;
});
return hasName && isObject(aiPluginManifest.api) && isObject(aiPluginManifest.auth) ? "pass" : "warn";
})();
const wikidataStatus = !urlOnly ? "na" : snapshot.external.wikidata ? "pass" : "warn";
const npmStatus = !urlOnly ? "na" : snapshot.external.npm ? "pass" : "warn";
const mcpRegistryStatus = !urlOnly ? "na" : snapshot.external.mcpRegistry ? "pass" : "warn";
return [

@@ -429,3 +570,7 @@ check("agents-md", agentsStatus, agentsPresent ? "AGENTS.md is available." : snapshot.agents.status === 0 ? "The AGENTS.md request failed." : "AGENTS.md is missing."),

check("link-headers", linkHeadersStatus, linkHeadersStatus === "pass" ? "The homepage returns RFC 8288 Link headers." : linkHeadersStatus === "na" ? "Not audited (no served web surface)." : "No Link header was found on the homepage."),
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.')
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.'),
check("ai-plugin", aiPluginStatus, aiPluginStatus === "pass" ? "A /.well-known/ai-plugin.json manifest is available." : aiPluginStatus === "na" ? "Not audited (no served web surface)." : "No usable /.well-known/ai-plugin.json manifest was found (missing, empty, or lacking identifying fields)."),
check("wikidata", wikidataStatus, wikidataStatus === "pass" ? "A Wikidata item links to this domain (P856)." : wikidataStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.wikidataError ? "The Wikidata lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No Wikidata item links to this domain via official website (P856)."),
check("npm-package", npmStatus, npmStatus === "pass" ? "A matching npm package is published." : npmStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.npmError ? "The npm registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No npm package matching the product was found."),
check("mcp-registry", mcpRegistryStatus, mcpRegistryStatus === "pass" ? "Listed in the official MCP registry." : mcpRegistryStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.mcpRegistryError ? "The MCP registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No entry matching this product was found in the official MCP registry.")
];

@@ -432,0 +577,0 @@ }

@@ -49,3 +49,3 @@ #!/usr/bin/env node

name: "aigentify",
version: "0.3.0",
version: "0.4.0",
mcpName: "io.github.pooriaarab/aigentify",

@@ -119,2 +119,10 @@ description: "Audit and generate agent-native product surfaces",

// src/audit.ts
var NO_EXTERNAL_DISCOVERY = {
wikidata: false,
wikidataError: false,
npm: false,
npmError: false,
mcpRegistry: false,
mcpRegistryError: false
};
function ok(status) {

@@ -149,3 +157,8 @@ return status >= 200 && status < 400;

"link-headers": 5,
"markdown-alt": 5
"markdown-alt": 5,
// External-discovery round 3 (queries third-party registries) — na for CLI/dir targets
"ai-plugin": 5,
wikidata: 5,
"npm-package": 5,
"mcp-registry": 5
};

@@ -174,3 +187,7 @@ var FIXES = {

"link-headers": "Return RFC 8288 Link headers on the homepage pointing at llms.txt, openapi.json, and the agent card so agents discover descriptors without parsing HTML.",
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.'
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.',
"ai-plugin": "Publish /.well-known/ai-plugin.json (the plugin manifest) so plugin hosts and agents can auto-discover your API and auth.",
wikidata: "Create a Wikidata item for the product with an official-website (P856) statement pointing at your domain, so agents can verify the entity.",
"npm-package": "Publish an official SDK/CLI to npm under a discoverable name so agents can install a typed client.",
"mcp-registry": "List your MCP server in the official MCP registry (server.json + mcp-publisher) so agents discover it by name."
};

@@ -274,2 +291,4 @@ function check(id, status, note) {

agentCard: { status: 404, contentType: "", text: "" },
aiPlugin: { status: 404, contentType: "", text: "" },
external: NO_EXTERNAL_DISCOVERY,
honestText,

@@ -293,2 +312,107 @@ hasMcp: Boolean(mcpFile || serverFile || hasMcpBin),

}
function productName(agentCard, home) {
const card = parseJson(agentCard.text);
if (card && typeof card.name === "string" && card.name.trim()) return card.name.trim();
const title = /<title[^>]*>([^<]{2,120})<\/title>/i.exec(home.text)?.[1]?.trim();
return title ? title.split(/\s[|–—-]\s/)[0].trim() : null;
}
var EXTERNAL_UA = "aigentify/0.4 (+https://github.com/pooriaarab/aigentify)";
var REGISTRY_ERROR = Symbol("registry-error");
async function fetchJson(fetcher, url) {
try {
const res = await fetcher(url, {
signal: AbortSignal.timeout(8e3),
headers: { accept: "application/json", "user-agent": EXTERNAL_UA }
});
if (!ok(res.status)) return REGISTRY_ERROR;
return await res.json();
} catch {
return REGISTRY_ERROR;
}
}
function normalize(s) {
return s.toLowerCase().replace(/[^a-z0-9]+/g, "");
}
function safeHost(url) {
try {
return new URL(url).host.replace(/^www\./i, "").toLowerCase();
} catch {
return "";
}
}
async function probeExternalDiscovery(fetcher, base, name) {
const host = (() => {
try {
return new URL(base).host.replace(/^www\./, "");
} catch {
return base;
}
})();
const productNorm = name ? normalize(name) : "";
const hostLabelNorm = normalize(host.split(".")[0]);
const wantNorms = [productNorm, hostLabelNorm].filter(Boolean);
const wikidataVariants = ["https://", "http://"].flatMap(
(scheme) => [host, `www.${host}`].flatMap((h) => ["", "/"].map((slash) => `${scheme}${h}${slash}`))
);
const wikidataP = Promise.all(
wikidataVariants.map(
(val) => fetchJson(
fetcher,
`https://www.wikidata.org/w/api.php?action=query&list=search&format=json&srsearch=${encodeURIComponent(
`haswbstatement:P856=${val}`
)}`
)
)
).then((results) => {
const found = results.some(
(d) => d !== REGISTRY_ERROR && (d?.query?.searchinfo?.totalhits ?? 0) > 0
);
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const npmQuery = name ?? host.split(".")[0];
const npmP = fetchJson(fetcher, `https://registry.npmjs.org/-/v1/search?size=20&text=${encodeURIComponent(npmQuery)}`).then((d) => {
if (d === REGISTRY_ERROR) return { found: false, errored: true };
const rawObjects = d?.objects;
const objects = Array.isArray(rawObjects) ? rawObjects : [];
const found = objects.some((o) => {
const pkgName = (o.package?.name ?? "").toLowerCase();
if (!pkgName) return false;
const scope = /^@([^/]+)\//.exec(pkgName)?.[1] ?? "";
const unscoped = pkgName.replace(/^@[^/]+\//, "");
const candidates = [pkgName, unscoped, scope].filter(Boolean).map(normalize);
const nameMatches = candidates.some((c) => wantNorms.includes(c));
const homepageHost = safeHost(o.package?.links?.homepage ?? "");
return nameMatches || homepageHost !== "" && homepageHost === host;
});
return { found, errored: false };
});
const slug = (name ?? host.split(".")[0]).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
const mcpP = Promise.all(
[slug, host].map((q) => fetchJson(fetcher, `https://registry.modelcontextprotocol.io/v0/servers?search=${encodeURIComponent(q)}`))
).then((results) => {
const found = results.some((d) => {
if (d === REGISTRY_ERROR) return false;
const rawServers = d?.servers;
const servers = Array.isArray(rawServers) ? rawServers : [];
return servers.some((entry) => {
const server = entry?.server ?? entry;
const rawName = String(server?.name ?? "");
const shortName = rawName.includes("/") ? rawName.slice(rawName.lastIndexOf("/") + 1) : rawName;
const websiteHost = safeHost(String(server?.websiteUrl ?? ""));
const repoHost = safeHost(String(server?.repository?.url ?? ""));
return wantNorms.includes(normalize(shortName)) || websiteHost === host || repoHost === host;
});
});
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const [wikidata, npm, mcpRegistry] = await Promise.all([wikidataP, npmP, mcpP]);
return {
wikidata: wikidata.found,
wikidataError: wikidata.errored,
npm: npm.found,
npmError: npm.errored,
mcpRegistry: mcpRegistry.found,
mcpRegistryError: mcpRegistry.errored
};
}
async function urlSnapshot(target, fetcher) {

@@ -312,3 +436,4 @@ const base = target.replace(/\/+$/, "");

authMd,
apiCatalog
apiCatalog,
aiPlugin
] = await Promise.all([

@@ -329,3 +454,4 @@ fetchText(fetcher, `${base}/agents.md`),

fetchText(fetcher, `${base}/auth.md`),
fetchText(fetcher, `${base}/.well-known/api-catalog`)
fetchText(fetcher, `${base}/.well-known/api-catalog`),
fetchText(fetcher, `${base}/.well-known/ai-plugin.json`)
]);

@@ -335,2 +461,3 @@ const agentCard = wellKnownCard;

const wk = ok(wellKnown.status) ? wellKnown : wellKnownCard;
const external = endpoints.every((item) => item.status === 0) ? NO_EXTERNAL_DISCOVERY : await probeExternalDiscovery(fetcher, base, productName(agentCard, home));
return {

@@ -352,2 +479,4 @@ agents,

agentCard,
aiPlugin,
external,
honestText: [home.text, offerBlock(agents.text)].join("\n"),

@@ -447,2 +576,14 @@ hasMcp: server.status >= 200 && server.status < 400 || mcp.status >= 200 && mcp.status < 400,

const markdownAltStatus = !urlOnly ? "na" : [...snapshot.home.text.matchAll(/<link\b[^>]*>/gi)].some(([tag]) => /rel=["']?alternate["']?/i.test(tag) && /type=["']?text\/markdown/i.test(tag)) ? "pass" : "warn";
const aiPluginManifest = (ok(snapshot.aiPlugin.status) ? parseJson(snapshot.aiPlugin.text) : void 0) ?? {};
const aiPluginStatus = !urlOnly ? "na" : (() => {
const isObject = (value) => typeof value === "object" && value !== null;
const hasName = ["name_for_model", "name_for_human"].some((key) => {
const value = aiPluginManifest[key];
return typeof value === "string" && value.trim().length > 0;
});
return hasName && isObject(aiPluginManifest.api) && isObject(aiPluginManifest.auth) ? "pass" : "warn";
})();
const wikidataStatus = !urlOnly ? "na" : snapshot.external.wikidata ? "pass" : "warn";
const npmStatus = !urlOnly ? "na" : snapshot.external.npm ? "pass" : "warn";
const mcpRegistryStatus = !urlOnly ? "na" : snapshot.external.mcpRegistry ? "pass" : "warn";
return [

@@ -470,3 +611,7 @@ check("agents-md", agentsStatus, agentsPresent ? "AGENTS.md is available." : snapshot.agents.status === 0 ? "The AGENTS.md request failed." : "AGENTS.md is missing."),

check("link-headers", linkHeadersStatus, linkHeadersStatus === "pass" ? "The homepage returns RFC 8288 Link headers." : linkHeadersStatus === "na" ? "Not audited (no served web surface)." : "No Link header was found on the homepage."),
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.')
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.'),
check("ai-plugin", aiPluginStatus, aiPluginStatus === "pass" ? "A /.well-known/ai-plugin.json manifest is available." : aiPluginStatus === "na" ? "Not audited (no served web surface)." : "No usable /.well-known/ai-plugin.json manifest was found (missing, empty, or lacking identifying fields)."),
check("wikidata", wikidataStatus, wikidataStatus === "pass" ? "A Wikidata item links to this domain (P856)." : wikidataStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.wikidataError ? "The Wikidata lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No Wikidata item links to this domain via official website (P856)."),
check("npm-package", npmStatus, npmStatus === "pass" ? "A matching npm package is published." : npmStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.npmError ? "The npm registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No npm package matching the product was found."),
check("mcp-registry", mcpRegistryStatus, mcpRegistryStatus === "pass" ? "Listed in the official MCP registry." : mcpRegistryStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.mcpRegistryError ? "The MCP registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No entry matching this product was found in the official MCP registry.")
];

@@ -473,0 +618,0 @@ }

@@ -15,3 +15,3 @@ #!/usr/bin/env node

name: "aigentify",
version: "0.3.0",
version: "0.4.0",
mcpName: "io.github.pooriaarab/aigentify",

@@ -85,2 +85,10 @@ description: "Audit and generate agent-native product surfaces",

// src/audit.ts
var NO_EXTERNAL_DISCOVERY = {
wikidata: false,
wikidataError: false,
npm: false,
npmError: false,
mcpRegistry: false,
mcpRegistryError: false
};
function ok(status) {

@@ -115,3 +123,8 @@ return status >= 200 && status < 400;

"link-headers": 5,
"markdown-alt": 5
"markdown-alt": 5,
// External-discovery round 3 (queries third-party registries) — na for CLI/dir targets
"ai-plugin": 5,
wikidata: 5,
"npm-package": 5,
"mcp-registry": 5
};

@@ -140,3 +153,7 @@ var FIXES = {

"link-headers": "Return RFC 8288 Link headers on the homepage pointing at llms.txt, openapi.json, and the agent card so agents discover descriptors without parsing HTML.",
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.'
"markdown-alt": 'Add a <link rel="alternate" type="text/markdown"> to the homepage so agents can find the markdown representation.',
"ai-plugin": "Publish /.well-known/ai-plugin.json (the plugin manifest) so plugin hosts and agents can auto-discover your API and auth.",
wikidata: "Create a Wikidata item for the product with an official-website (P856) statement pointing at your domain, so agents can verify the entity.",
"npm-package": "Publish an official SDK/CLI to npm under a discoverable name so agents can install a typed client.",
"mcp-registry": "List your MCP server in the official MCP registry (server.json + mcp-publisher) so agents discover it by name."
};

@@ -240,2 +257,4 @@ function check(id, status, note) {

agentCard: { status: 404, contentType: "", text: "" },
aiPlugin: { status: 404, contentType: "", text: "" },
external: NO_EXTERNAL_DISCOVERY,
honestText,

@@ -259,2 +278,107 @@ hasMcp: Boolean(mcpFile || serverFile || hasMcpBin),

}
function productName(agentCard, home) {
const card = parseJson(agentCard.text);
if (card && typeof card.name === "string" && card.name.trim()) return card.name.trim();
const title = /<title[^>]*>([^<]{2,120})<\/title>/i.exec(home.text)?.[1]?.trim();
return title ? title.split(/\s[|–—-]\s/)[0].trim() : null;
}
var EXTERNAL_UA = "aigentify/0.4 (+https://github.com/pooriaarab/aigentify)";
var REGISTRY_ERROR = Symbol("registry-error");
async function fetchJson(fetcher, url) {
try {
const res = await fetcher(url, {
signal: AbortSignal.timeout(8e3),
headers: { accept: "application/json", "user-agent": EXTERNAL_UA }
});
if (!ok(res.status)) return REGISTRY_ERROR;
return await res.json();
} catch {
return REGISTRY_ERROR;
}
}
function normalize(s) {
return s.toLowerCase().replace(/[^a-z0-9]+/g, "");
}
function safeHost(url) {
try {
return new URL(url).host.replace(/^www\./i, "").toLowerCase();
} catch {
return "";
}
}
async function probeExternalDiscovery(fetcher, base, name) {
const host = (() => {
try {
return new URL(base).host.replace(/^www\./, "");
} catch {
return base;
}
})();
const productNorm = name ? normalize(name) : "";
const hostLabelNorm = normalize(host.split(".")[0]);
const wantNorms = [productNorm, hostLabelNorm].filter(Boolean);
const wikidataVariants = ["https://", "http://"].flatMap(
(scheme) => [host, `www.${host}`].flatMap((h) => ["", "/"].map((slash) => `${scheme}${h}${slash}`))
);
const wikidataP = Promise.all(
wikidataVariants.map(
(val) => fetchJson(
fetcher,
`https://www.wikidata.org/w/api.php?action=query&list=search&format=json&srsearch=${encodeURIComponent(
`haswbstatement:P856=${val}`
)}`
)
)
).then((results) => {
const found = results.some(
(d) => d !== REGISTRY_ERROR && (d?.query?.searchinfo?.totalhits ?? 0) > 0
);
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const npmQuery = name ?? host.split(".")[0];
const npmP = fetchJson(fetcher, `https://registry.npmjs.org/-/v1/search?size=20&text=${encodeURIComponent(npmQuery)}`).then((d) => {
if (d === REGISTRY_ERROR) return { found: false, errored: true };
const rawObjects = d?.objects;
const objects = Array.isArray(rawObjects) ? rawObjects : [];
const found = objects.some((o) => {
const pkgName = (o.package?.name ?? "").toLowerCase();
if (!pkgName) return false;
const scope = /^@([^/]+)\//.exec(pkgName)?.[1] ?? "";
const unscoped = pkgName.replace(/^@[^/]+\//, "");
const candidates = [pkgName, unscoped, scope].filter(Boolean).map(normalize);
const nameMatches = candidates.some((c) => wantNorms.includes(c));
const homepageHost = safeHost(o.package?.links?.homepage ?? "");
return nameMatches || homepageHost !== "" && homepageHost === host;
});
return { found, errored: false };
});
const slug = (name ?? host.split(".")[0]).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
const mcpP = Promise.all(
[slug, host].map((q) => fetchJson(fetcher, `https://registry.modelcontextprotocol.io/v0/servers?search=${encodeURIComponent(q)}`))
).then((results) => {
const found = results.some((d) => {
if (d === REGISTRY_ERROR) return false;
const rawServers = d?.servers;
const servers = Array.isArray(rawServers) ? rawServers : [];
return servers.some((entry) => {
const server = entry?.server ?? entry;
const rawName = String(server?.name ?? "");
const shortName = rawName.includes("/") ? rawName.slice(rawName.lastIndexOf("/") + 1) : rawName;
const websiteHost = safeHost(String(server?.websiteUrl ?? ""));
const repoHost = safeHost(String(server?.repository?.url ?? ""));
return wantNorms.includes(normalize(shortName)) || websiteHost === host || repoHost === host;
});
});
return { found, errored: !found && results.some((d) => d === REGISTRY_ERROR) };
});
const [wikidata, npm, mcpRegistry] = await Promise.all([wikidataP, npmP, mcpP]);
return {
wikidata: wikidata.found,
wikidataError: wikidata.errored,
npm: npm.found,
npmError: npm.errored,
mcpRegistry: mcpRegistry.found,
mcpRegistryError: mcpRegistry.errored
};
}
async function urlSnapshot(target, fetcher) {

@@ -278,3 +402,4 @@ const base = target.replace(/\/+$/, "");

authMd,
apiCatalog
apiCatalog,
aiPlugin
] = await Promise.all([

@@ -295,3 +420,4 @@ fetchText(fetcher, `${base}/agents.md`),

fetchText(fetcher, `${base}/auth.md`),
fetchText(fetcher, `${base}/.well-known/api-catalog`)
fetchText(fetcher, `${base}/.well-known/api-catalog`),
fetchText(fetcher, `${base}/.well-known/ai-plugin.json`)
]);

@@ -301,2 +427,3 @@ const agentCard = wellKnownCard;

const wk = ok(wellKnown.status) ? wellKnown : wellKnownCard;
const external = endpoints.every((item) => item.status === 0) ? NO_EXTERNAL_DISCOVERY : await probeExternalDiscovery(fetcher, base, productName(agentCard, home));
return {

@@ -318,2 +445,4 @@ agents,

agentCard,
aiPlugin,
external,
honestText: [home.text, offerBlock(agents.text)].join("\n"),

@@ -413,2 +542,14 @@ hasMcp: server.status >= 200 && server.status < 400 || mcp.status >= 200 && mcp.status < 400,

const markdownAltStatus = !urlOnly ? "na" : [...snapshot.home.text.matchAll(/<link\b[^>]*>/gi)].some(([tag]) => /rel=["']?alternate["']?/i.test(tag) && /type=["']?text\/markdown/i.test(tag)) ? "pass" : "warn";
const aiPluginManifest = (ok(snapshot.aiPlugin.status) ? parseJson(snapshot.aiPlugin.text) : void 0) ?? {};
const aiPluginStatus = !urlOnly ? "na" : (() => {
const isObject = (value) => typeof value === "object" && value !== null;
const hasName = ["name_for_model", "name_for_human"].some((key) => {
const value = aiPluginManifest[key];
return typeof value === "string" && value.trim().length > 0;
});
return hasName && isObject(aiPluginManifest.api) && isObject(aiPluginManifest.auth) ? "pass" : "warn";
})();
const wikidataStatus = !urlOnly ? "na" : snapshot.external.wikidata ? "pass" : "warn";
const npmStatus = !urlOnly ? "na" : snapshot.external.npm ? "pass" : "warn";
const mcpRegistryStatus = !urlOnly ? "na" : snapshot.external.mcpRegistry ? "pass" : "warn";
return [

@@ -436,3 +577,7 @@ check("agents-md", agentsStatus, agentsPresent ? "AGENTS.md is available." : snapshot.agents.status === 0 ? "The AGENTS.md request failed." : "AGENTS.md is missing."),

check("link-headers", linkHeadersStatus, linkHeadersStatus === "pass" ? "The homepage returns RFC 8288 Link headers." : linkHeadersStatus === "na" ? "Not audited (no served web surface)." : "No Link header was found on the homepage."),
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.')
check("markdown-alt", markdownAltStatus, markdownAltStatus === "pass" ? "The homepage advertises a markdown alternate link." : markdownAltStatus === "na" ? "Not audited (no served web surface)." : 'No <link rel="alternate" type="text/markdown"> was found.'),
check("ai-plugin", aiPluginStatus, aiPluginStatus === "pass" ? "A /.well-known/ai-plugin.json manifest is available." : aiPluginStatus === "na" ? "Not audited (no served web surface)." : "No usable /.well-known/ai-plugin.json manifest was found (missing, empty, or lacking identifying fields)."),
check("wikidata", wikidataStatus, wikidataStatus === "pass" ? "A Wikidata item links to this domain (P856)." : wikidataStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.wikidataError ? "The Wikidata lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No Wikidata item links to this domain via official website (P856)."),
check("npm-package", npmStatus, npmStatus === "pass" ? "A matching npm package is published." : npmStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.npmError ? "The npm registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No npm package matching the product was found."),
check("mcp-registry", mcpRegistryStatus, mcpRegistryStatus === "pass" ? "Listed in the official MCP registry." : mcpRegistryStatus === "na" ? "Not audited (no served web surface)." : snapshot.external.mcpRegistryError ? "The MCP registry lookup could not be completed (registry error or timeout) \u2014 not a confirmed gap." : "No entry matching this product was found in the official MCP registry.")
];

@@ -439,0 +584,0 @@ }

+1
-1
{
"name": "aigentify",
"version": "0.3.0",
"version": "0.4.0",
"mcpName": "io.github.pooriaarab/aigentify",

@@ -5,0 +5,0 @@ "description": "Audit and generate agent-native product surfaces",

@@ -66,3 +66,3 @@ # aigentify

`RateLimit-*` headers), plus `auth-md`, `api-catalog` (RFC 9727),
`agent-card-a2a` (A2A), `link-headers` (RFC 8288), and `markdown-alt`.
`agent-card-a2a` (A2A), `link-headers` (RFC 8288), `markdown-alt`, plus **external-discovery** checks that query third-party registries — `ai-plugin`, `wikidata` (P856 official-website match), `npm-package`, and `mcp-registry`.

@@ -69,0 +69,0 @@ ## Configuration

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display