UNPKG

@agentcommunity/aid-doctor

Version:

CLI tool for Agent Identity & Discovery (AID) - validate and check AID records

672 lines (667 loc) 28.2 kB
#!/usr/bin/env node import { Command } from 'commander'; import { writeFile } from 'fs/promises'; import { realpathSync, readFileSync, promises } from 'fs'; import path2 from 'path'; import os from 'os'; import { fileURLToPath } from 'url'; import { AidError, PROTOCOL_TOKENS, AUTH_TOKENS } from '@agentcommunity/aid'; import { runCheck, verifyPka, buildTxtRecordVariant, validateTxtRecord, generateEd25519KeyPair, classifySecurityChange as classifySecurityChange$1, shouldRejectForFailPolicy as shouldRejectForFailPolicy$1, derivePkaKeyid as derivePkaKeyid$1, ERROR_MESSAGES } from '@agentcommunity/aid-engine'; import chalk2 from 'chalk'; import ora from 'ora'; import inquirer from 'inquirer'; import clipboardy from 'clipboardy'; var CACHE_SCHEMA_VERSION = 3; function cachePath() { return path2.join(os.homedir(), ".aid", "cache.json"); } var derivePkaKeyid = derivePkaKeyid$1; function isCacheEntry(value) { if (!value || typeof value !== "object") return false; const entry = value; return typeof entry.lastSeen === "string" && (typeof entry.pka === "string" || entry.pka === null) && (typeof entry.kid === "string" || entry.kid === null); } function migrateEntry(entry) { const keyMaterial = derivePkaKeyid(entry.pka); return { ...entry, version: entry.version ?? (entry.pka?.startsWith("z") ? "aid1" : entry.pka ? "aid2" : null), trustSource: entry.trustSource ?? "dns", keyid: entry.keyid ?? keyMaterial?.keyid ?? null, jwkX: entry.jwkX ?? keyMaterial?.jwkX ?? null, domainBound: entry.domainBound ?? null }; } function migrateCacheFile(raw) { if (raw && typeof raw === "object" && raw.schemaVersion === CACHE_SCHEMA_VERSION && raw.entries && typeof raw.entries === "object") { return raw; } const entries = {}; if (!raw || typeof raw !== "object") { return { schemaVersion: CACHE_SCHEMA_VERSION, entries }; } const wrapped = raw; const source = wrapped.entries && typeof wrapped.entries === "object" ? wrapped.entries : raw; for (const [domain, entry] of Object.entries(source)) { if (domain === "schemaVersion" || domain === "entries") continue; if (isCacheEntry(entry)) { entries[domain] = migrateEntry(entry); } } return { schemaVersion: CACHE_SCHEMA_VERSION, entries }; } function buildCacheEntryFromReport(report, now = /* @__PURE__ */ new Date()) { const record = report.record.parsed; const keyMaterial = derivePkaKeyid(record?.pka ?? null); return { lastSeen: now.toISOString(), version: record?.v ?? null, trustSource: report.queried.wellKnown.used ? "well-known-tls" : "dns", pka: record?.pka ?? null, kid: record?.v === "aid1" ? record.kid ?? null : null, keyid: keyMaterial?.keyid ?? null, jwkX: keyMaterial?.jwkX ?? null, domainBound: report.pka.domainBound ?? null, hash: report.cacheEntry?.hash ?? null }; } var classifySecurityChange = classifySecurityChange$1; var shouldRejectForFailPolicy = shouldRejectForFailPolicy$1; async function loadCache() { try { const p = cachePath(); const data = await promises.readFile(p, "utf8"); return migrateCacheFile(JSON.parse(data)); } catch { return { schemaVersion: CACHE_SCHEMA_VERSION, entries: {} }; } } async function ensureDir(filePath) { try { await promises.mkdir(path2.dirname(filePath), { recursive: true }); } catch { } } async function saveCache(cache) { const p = cachePath(); await ensureDir(p); const tmp = p + ".tmp"; const content = JSON.stringify(migrateCacheFile(cache), null, 2); await promises.writeFile(tmp, content, { mode: 384 }); await promises.rename(tmp, p); } // src/output.ts function generateActionableSuggestions(report) { const suggestions = []; const { record, dnssec, tls, pka } = report; const icon = chalk2.blue("\u{1F4A1}"); if (record.raw) { const byteLen = new TextEncoder().encode(record.raw).length; if (byteLen > 255) { suggestions.push( `${icon} ${chalk2.bold("Reduce record size:")} ${ERROR_MESSAGES.BYTE_LIMIT_EXCEEDED}` ); } } if (record.warnings.some((warning) => warning.code === "LONG_KEY_COMPAT")) { suggestions.push( `${icon} ${chalk2.bold("Canonicalize TXT keys:")} ${ERROR_MESSAGES.USE_ALIASES}` ); } if (!dnssec.present) { suggestions.push(`${icon} ${chalk2.bold("Enable DNSSEC:")} ${ERROR_MESSAGES.ENABLE_DNSSEC}`); } if (!pka.present) { suggestions.push(`${icon} ${chalk2.bold("Add endpoint proof:")} ${ERROR_MESSAGES.ADD_PKA}`); } if (record.parsed?.v === "aid2" && pka.present && pka.verified && pka.domainBound === false) { suggestions.push( `${icon} ${chalk2.bold("Enable domain binding:")} Your endpoint proof is valid but not domain-bound. Configure your endpoint to cover the AID-Domain header in its HTTP Message Signature so the endpoint attests that it serves the queried domain.` ); } if (tls.checked && tls.valid && tls.daysRemaining !== null && tls.daysRemaining < 21) { suggestions.push(`${icon} ${chalk2.bold("Renew TLS certificate:")} ${ERROR_MESSAGES.RENEW_TLS}`); } if (record.errors.some((e) => e.code === "ERR_UNSUPPORTED_PROTO")) { suggestions.push( `${icon} ${chalk2.bold("Check protocol support:")} The protocol in your record is not supported by this client. See the official registry for valid tokens.` ); } return suggestions; } function formatCheckResult(report) { const { domain, record, queried, tls, dnssec, pka, downgrade } = report; const lines = []; const icons = { ok: chalk2.green("\u2705"), error: chalk2.red("\u274C"), warn: chalk2.yellow("\u26A0\uFE0F"), info: chalk2.blue("\u{1F4A1}") }; const finalQueryName = queried.attempts[queried.attempts.length - 1]?.name ?? `_agent.${domain}`; const dnsAttempt = queried.attempts.find((a) => a.result === "NOERROR"); if (dnsAttempt) { const ttl = dnsAttempt.ttl ?? "N/A"; const bytes = new TextEncoder().encode(record.raw ?? "").length; const source = queried.wellKnown.used ? "(.well-known)" : "(DNS)"; lines.push( `[1/6] DNS TXT ${chalk2.cyan(finalQueryName)} ... ${icons.ok} Found ${source} (TTL ${ttl}, ${bytes} bytes)` ); } else { lines.push(`[1/6] DNS TXT ${chalk2.cyan(finalQueryName)} ... ${icons.error} Not Found`); } if (record.valid) { lines.push(`[2/6] Record validation ... ${icons.ok} Valid`); } else { const msg = record.errors[0]?.message ?? "Invalid structure"; lines.push(`[2/6] Record validation ... ${icons.error} ${msg}`); } if (dnssec.present) { lines.push(`[3/6] DNSSEC (RRSIG) ... ${icons.ok} Detected`); } else { lines.push(`[3/6] DNSSEC (RRSIG) ... ${icons.info} Not detected`); } if (tls.checked) { if (tls.valid) { const expiry = tls.daysRemaining; lines.push( `[4/6] TLS ${chalk2.cyan(tls.host ?? "")} ... ${icons.ok} Valid (SAN matches, expires in ${expiry} days)` ); } else { const reason = record.errors.find((e) => e.code.startsWith("ERR_SECURITY"))?.message ?? "TLS validation failed"; lines.push(`[4/6] TLS ... ${icons.error} ${reason}`); } } else { lines.push(`[4/6] TLS ... ${chalk2.gray("Skipped (local protocol)")}`); } if (pka.present) { if (pka.verified) { const keyLabel = record.parsed?.v === "aid2" ? `keyid=${derivePkaKeyid(record.parsed.pka ?? null)?.keyid ?? "unknown"}` : `legacy kid=${pka.kid ?? record.parsed?.kid ?? "unknown"}`; const isAid2 = record.parsed?.v === "aid2"; const bindingLabel = pka.domainBound === true ? ", domain-bound" : isAid2 && pka.domainBound === false ? ", endpoint-proof only" : ""; lines.push( `[5/6] PKA handshake ... ${icons.ok} Verified (alg=${pka.alg}, ${keyLabel}${bindingLabel})` ); } else { const reason = record.errors.find((e) => e.message.includes("PKA"))?.message ?? "Verification failed"; lines.push(`[5/6] PKA handshake ... ${icons.error} ${reason}`); } } else { lines.push(`[5/6] PKA handshake ... ${icons.info} Not present`); } if (downgrade.checked) { const status = downgrade.status; if (status === "downgrade" || status === "pka_removed") { lines.push(`[6/6] Downgrade check ... ${icons.warn} PKA removed`); } else if (status === "key_rotation" || status === "key_replaced") { lines.push(`[6/6] Downgrade check ... ${icons.warn} PKA key replaced`); } else if (status === "pka_added") { lines.push(`[6/6] Downgrade check ... ${icons.ok} PKA added`); } else if (status === "version_downgrade") { lines.push(`[6/6] Downgrade check ... ${icons.warn} AID version downgrade aid2->aid1`); } else if (status === "binding_loss") { lines.push( `[6/6] Downgrade check ... ${icons.warn} Domain-binding lost (endpoint-proof only)` ); } else if (status === "fallback_well_known_tls") { lines.push( `[6/6] Downgrade check ... ${icons.warn} DNS failed; using TLS-hosted fallback metadata (trustSource=well-known-tls)` ); } else { lines.push( `[6/6] Downgrade check ... ${icons.ok} ${status === "first_seen" ? "First seen" : "No change"}` ); } } else { lines.push(`[6/6] Downgrade check ... ${chalk2.gray("Skipped")}`); } lines.push("\n--- Summary ---"); if (report.exitCode === 0) { const warnings = record.warnings.filter((w) => w.code !== "PROTOCOL_SUBDOMAIN_EXISTS"); if (warnings.length > 0) { lines.push(`${icons.warn} Record is valid but has ${warnings.length} warning(s).`); for (const warn of warnings) { lines.push(` - ${warn.message}`); } } else { lines.push(`${icons.ok} Record is valid and secure.`); } } else { lines.push( `${icons.error} Found ${report.record.errors.length} error(s) and ${record.warnings.length} warning(s).` ); } const suggestions = generateActionableSuggestions(report); if (suggestions.length > 0) { lines.push("\n--- Recommendations ---"); lines.push(...suggestions); } return lines.join("\n"); } // src/security-state.ts function applySecurityState(report, previousCacheEntry, downgradePolicy) { if (!report.record.parsed) return { shouldPersist: false }; if (report.exitCode !== 0 || report.pka.verified === false) { report.cacheEntry = null; return { shouldPersist: false }; } const currentEntry = buildCacheEntryFromReport(report); const status = classifySecurityChange(previousCacheEntry, currentEntry); report.downgrade.checked = true; report.downgrade.previous = previousCacheEntry ? { pka: previousCacheEntry.pka, kid: previousCacheEntry.kid, keyid: previousCacheEntry.keyid ?? null, version: previousCacheEntry.version ?? null, trustSource: previousCacheEntry.trustSource ?? "dns" } : null; report.downgrade.status = status; const warningByStatus = { pka_removed: { code: "PKA_REMOVED", message: "Previously present PKA was removed." }, key_replaced: { code: "KEY_REPLACED", message: "Previously observed PKA key was replaced." }, version_downgrade: { code: "VERSION_DOWNGRADE", message: "Previously observed aid2 record is now aid1." }, fallback_well_known_tls: { code: "FALLBACK_WELL_KNOWN_TLS", message: "DNS failed and .well-known supplied TLS-hosted metadata." }, pka_added: { code: "PKA_ADDED", message: "PKA endpoint proof was added since the previous check." }, binding_loss: { code: "BINDING_LOSS", message: "Domain-binding proof was present in the previous check but is now absent (endpoint-proof only)." } }; const warning = warningByStatus[status]; if (warning) { report.record.warnings.push(warning); } if (downgradePolicy === "fail" && shouldRejectForFailPolicy(status, previousCacheEntry)) { report.exitCode = 1003; report.cacheEntry = null; return { shouldPersist: false }; } report.cacheEntry = currentEntry; return { shouldPersist: true }; } // src/cli.ts function readPackageVersion() { try { const pkgUrl = new URL("../package.json", import.meta.url); const fileContents = readFileSync(pkgUrl, "utf8"); const pkg = JSON.parse(fileContents); return pkg.version ?? "0.0.0"; } catch { return "0.0.0"; } } async function generateEd25519(label, outDir) { const { publicKey, privateKeyPem } = await generateEd25519KeyPair(); const dir = outDir || path2.join(os.homedir(), ".aid", "keys"); await promises.mkdir(dir, { recursive: true }); const name = (label || "key") + "-ed25519.key"; const privatePath = path2.join(dir, name); await promises.writeFile(privatePath, privateKeyPem, { mode: 384 }); return { publicKey, privatePath }; } function createCliProgram() { const program = new Command(); program.name("aid-doctor").description("CLI tool for Agent Identity & Discovery (AID)").version(readPackageVersion()); function formatError(error, domain) { if (error instanceof AidError) { const codeColor = error.code >= 1003 ? chalk2.red : chalk2.yellow; return [ chalk2.red(`\u274C AID Discovery Failed for ${domain}`), "", ` Error Code: ${codeColor(error.code)} (${error.errorCode})`, ` Message: ${error.message}` ].join("\n"); } if (error instanceof Error) { return [chalk2.red(`\u274C Unexpected Error for ${domain}`), "", ` ${error.message}`].join("\n"); } return [chalk2.red(`\u274C Unexpected Error for ${domain}`), "", ` ${String(error)}`].join("\n"); } program.command("check <domain>").description("Check a domain for AID records and display a human-readable report").option("-p, --protocol <protocol>", "Diagnostics hint only; base-first remains canonical").option( "--probe-proto-subdomain", "If base TXT is missing and --protocol set, probe _agent._<proto>.<domain>", false ).option( "--probe-proto-even-if-base", "Probe proto subdomain even when base exists (diagnostics only)", false ).option("-t, --timeout <ms>", "DNS query timeout in milliseconds", "5000").option("--no-fallback", "Disable .well-known fallback on DNS miss").option("--fallback-timeout <ms>", "Timeout for .well-known fetch (ms)", "2000").option("--security-mode <mode>", "Security preset: balanced | strict").option("--dnssec <policy>", "DNSSEC policy: off | prefer | require").option("--pka-policy <policy>", "PKA policy: if-present | require").option("--downgrade-policy <policy>", "Downgrade policy: off | warn | fail").option("--well-known-policy <policy>", "Well-known policy: auto | disable").option("--domain-binding <policy>", "Domain binding policy: off | prefer | require").option("--show-details", "Show TLS/DNSSEC/PKA short details", false).option("--dump-well-known [path]", "On fallback failure, print or save body snippet", false).option( "--check-downgrade", "Consult cache and warn when pka has been removed or changed", false ).option("--no-color", "Disable ANSI color output").option("--code", "Exit with the specific error code on failure (for scripting)").action( async (domain, options) => { if (options.noColor) { chalk2.level = 0; } const spinner = ora(`Checking AID record for ${domain}...`).start(); try { const cache = options.checkDowngrade ? await loadCache() : null; const previousCacheEntry = cache ? cache.entries[domain] : void 0; const report = await runCheck(domain, { protocol: options.protocol, timeoutMs: Number.parseInt(options.timeout), allowFallback: options.fallback !== false, wellKnownTimeoutMs: Number.parseInt(options.fallbackTimeout || "2000"), securityMode: options.securityMode, dnssecPolicy: options.dnssec, pkaPolicy: options.pkaPolicy, downgradePolicy: options.downgradePolicy, wellKnownPolicy: options.wellKnownPolicy, ...options.domainBinding ? { domainBindingPolicy: options.domainBinding } : {}, showDetails: options.showDetails, probeProtoSubdomain: options.probeProtoSubdomain, probeProtoEvenIfBase: options.probeProtoEvenIfBase, dumpWellKnownPath: typeof options.dumpWellKnown === "string" ? options.dumpWellKnown : null, checkDowngrade: false }); const securityState = cache ? applySecurityState(report, previousCacheEntry, options.downgradePolicy) : null; if (cache && securityState?.shouldPersist && report.cacheEntry) { cache.entries[domain] = report.cacheEntry; await saveCache(cache); } spinner.stop(); console.log(formatCheckResult(report)); process.exit(options.code ? report.exitCode : report.exitCode === 0 ? 0 : 1); } catch (error) { spinner.stop(); console.log(formatError(error, domain)); if (options.code && error instanceof AidError) { process.exit(error.code); } else { process.exit(1); } } } ); program.command("json <domain>").description("Check a domain for AID records and output machine-readable JSON").option("-p, --protocol <protocol>", "Diagnostics hint only; base-first remains canonical").option("-t, --timeout <ms>", "DNS query timeout in milliseconds", "5000").option("--no-fallback", "Disable .well-known fallback on DNS miss").option("--fallback-timeout <ms>", "Timeout for .well-known fetch (ms)", "2000").option("--security-mode <mode>", "Security preset: balanced | strict").option("--dnssec <policy>", "DNSSEC policy: off | prefer | require").option("--pka-policy <policy>", "PKA policy: if-present | require").option("--downgrade-policy <policy>", "Downgrade policy: off | warn | fail").option("--well-known-policy <policy>", "Well-known policy: auto | disable").option("--domain-binding <policy>", "Domain binding policy: off | prefer | require").option("--show-details", "Show TLS/DNSSEC/PKA short details", false).option("--dump-well-known [path]", "On fallback failure, print or save body snippet", false).option( "--check-downgrade", "Consult cache and warn when pka has been removed or changed", false ).option("--no-color", "Disable ANSI color output").option("--code", "Exit with the specific error code on failure (for scripting)").action( async (domain, options) => { try { const cache = options.checkDowngrade ? await loadCache() : null; const previousCacheEntry = cache ? cache.entries[domain] : void 0; const report = await runCheck(domain, { protocol: options.protocol, timeoutMs: Number.parseInt(options.timeout), allowFallback: options.fallback !== false, wellKnownTimeoutMs: Number.parseInt(options.fallbackTimeout || "2000"), securityMode: options.securityMode, dnssecPolicy: options.dnssec, pkaPolicy: options.pkaPolicy, downgradePolicy: options.downgradePolicy, wellKnownPolicy: options.wellKnownPolicy, ...options.domainBinding ? { domainBindingPolicy: options.domainBinding } : {}, showDetails: options.showDetails || false, probeProtoSubdomain: false, // JSON command doesn't support proto probing for now probeProtoEvenIfBase: false, dumpWellKnownPath: typeof options.dumpWellKnown === "string" ? options.dumpWellKnown : null, checkDowngrade: false }); const securityState = cache ? applySecurityState(report, previousCacheEntry, options.downgradePolicy) : null; if (cache && securityState?.shouldPersist && report.cacheEntry) { cache.entries[domain] = report.cacheEntry; await saveCache(cache); } console.log(JSON.stringify(report, null, 2)); process.exit(options.code ? report.exitCode : report.exitCode === 0 ? 0 : 1); } catch (error) { if (error instanceof AidError) { console.log( JSON.stringify( { success: false, domain, error: { code: error.code, errorCode: error.errorCode, message: error.message }, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2 ) ); process.exit(options.code ? error.code : 1); } else if (error instanceof Error) { console.log( JSON.stringify( { success: false, domain, error: { code: 1, errorCode: "UNKNOWN_ERROR", message: error.message }, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2 ) ); process.exit(1); } else { console.log( JSON.stringify( { success: false, domain, error: { code: 1, errorCode: "UNKNOWN_ERROR", message: String(error) }, timestamp: (/* @__PURE__ */ new Date()).toISOString() }, null, 2 ) ); process.exit(1); } } } ); program.command("pka").description("PKA key helpers").addCommand( new Command("generate").description( "Generate a new Ed25519 keypair and print the AID v2 public key (base64url JWK x); private key saved to ~/.aid/keys" ).option("--label <name>", "Key label (filename prefix)").option("--out <dir>", "Output directory for private key").option("--print-private", "Also print private key PEM to stdout (not recommended)", false).action(async (opts) => { const { publicKey, privatePath } = await generateEd25519(opts.label, opts.out); console.log(publicKey); console.error(`Saved private key to ${privatePath}`); if (opts.printPrivate) { console.error("Printing private key was requested; handle with care."); } }) ).addCommand( new Command("verify").description("Verify an AID v2 PKA public key string").requiredOption("--key <pka>", "base64url JWK x for a raw Ed25519 public key").action((opts) => { const res = verifyPka(opts.key); if (res.valid) console.log("\u2705 valid"); else { console.log("\u274C invalid" + (res.reason ? `: ${res.reason}` : "")); process.exitCode = 1; } }) ); program.command("generate").description("Run an interactive prompt to generate a new AID record").option("--save-draft <path>", "Save the generated record to a file").action(async (opts) => { console.log(chalk2.bold.cyan("AID Record Generator")); console.log( chalk2.gray("This tool will guide you through creating a valid DNS TXT record.\n") ); const answers = await inquirer.prompt([ { type: "input", name: "domain", message: "What is the primary domain for your agent?", validate: (input) => input ? true : "Domain cannot be empty." }, { type: "input", name: "uri", message: "What is the full URI of your agent's endpoint?", validate: (input) => { try { new URL(input); return true; } catch { return "Please enter a valid URI (e.g., https://api.example.com/agent)."; } } }, { type: "list", name: "proto", message: "Select the communication protocol:", choices: Object.keys(PROTOCOL_TOKENS), default: "mcp" }, { type: "list", name: "auth", message: "Select the authentication method (if any):", choices: ["none", ...Object.keys(AUTH_TOKENS)], filter: (val) => val === "none" ? "" : val }, { type: "input", name: "desc", message: "Enter a short description (optional):", validate: (input) => { if (!input) return true; const byteLength = new TextEncoder().encode(input).length; return byteLength <= 60 || `Description is ${byteLength} bytes (max 60).`; } }, { type: "input", name: "docs", message: "Docs URL (https, optional):", validate: (input) => { if (!input) return true; try { const u = new URL(input); return u.protocol === "https:" || "Docs must be https:// URL"; } catch { return "Docs must be https:// URL"; } } }, { type: "input", name: "dep", message: "Deprecation date (ISO 8601 UTC, e.g., 2026-01-01T00:00:00Z, optional):", validate: (input) => { if (!input) return true; return /Z$/.test(input) && !Number.isNaN(Date.parse(input)) || "Must be ISO 8601 UTC with Z"; } }, { type: "confirm", name: "addPka", message: "Add PKA endpoint proof now?", default: false }, { type: "input", name: "pka", message: "PKA public key (base64url JWK x for Ed25519, AID v2):", when: (a) => Boolean(a.addPka), validate: (input) => { if (!input) return "PKA key required when adding PKA"; const r = verifyPka(input); return r.valid || r.reason || "Invalid"; } } ]); const formData = answers; const txtRecord = buildTxtRecordVariant(formData, true); const validation = validateTxtRecord(txtRecord); console.log(chalk2.green("\n--- Generation Complete ---\n")); console.log(chalk2.bold("Host:")); console.log(chalk2.yellow(`_agent.${formData.domain}`)); console.log(""); console.log(chalk2.bold("Type:")); console.log(chalk2.yellow("TXT")); console.log(""); console.log(chalk2.bold("Value:")); console.log(chalk2.yellow(txtRecord)); console.log(""); if (validation.isValid) { try { await clipboardy.write(txtRecord); console.log( chalk2.bold.green("\u2705 Success! The TXT record value has been copied to your clipboard.") ); } catch { console.log(chalk2.red("Could not copy to clipboard. Please copy the value manually.")); } if (opts.saveDraft) { try { await writeFile(opts.saveDraft, txtRecord, "utf8"); console.log(chalk2.green(`\u{1F4BE} Draft saved to ${opts.saveDraft}`)); } catch (error) { if (error instanceof Error) { console.log(chalk2.red(`Could not save draft: ${error.message}`)); } else { console.log(chalk2.red(`Could not save draft: ${String(error)}`)); } } } } else { console.log(chalk2.bold.red(`\u{1F525} Validation Failed: ${validation.error}`)); } }); program.command("version").description("Show version information").action(async () => { const version = readPackageVersion(); console.log( [ chalk2.bold("aid-doctor") + " - Agent Identity & Discovery CLI", `Version: ${chalk2.green(version)}`, `Node: ${chalk2.gray(process.version)}`, `Platform: ${chalk2.gray(process.platform)}`, "", "For more information, visit:", chalk2.blue("https://aid.agentcommunity.org") ].join("\n") ); }); return program; } function isDirectCliInvocation(argvPath, modulePath = fileURLToPath(import.meta.url)) { if (!argvPath) { return false; } const resolvedArgvPath = path2.resolve(argvPath); try { return realpathSync(modulePath) === realpathSync(resolvedArgvPath); } catch { return modulePath === resolvedArgvPath; } } var isDirectCliRun = isDirectCliInvocation(process.argv[1]); if (isDirectCliRun) { createCliProgram().parse(); } export { createCliProgram, isDirectCliInvocation }; //# sourceMappingURL=cli.js.map //# sourceMappingURL=cli.js.map